2009-05-12 23 views
137

Tengo un problema cuando intento agregar restricciones a mis tablas. Me sale el error:¿La restricción de clave externa puede causar ciclos o múltiples rutas en cascada?

Introducing FOREIGN KEY constraint 'FK74988DB24B3C886' on table 'Employee' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

Mi restricción es entre una tabla y una tabla de Codeemployee. La tabla Code contiene Id, Name, FriendlyName, Type y Value. El employee tiene una cantidad de campos que hacen referencia a los códigos, de modo que puede haber una referencia para cada tipo de código.

Necesito que los campos se establezcan como nulos si se elimina el código al que se hace referencia.

¿Alguna idea de cómo puedo hacer esto?

+0

Una de la solución es [aquí] (http://stackoverflow.com/a/5828969/148271) – IsmailS

Respuesta

146

SQL Server realiza un recuento sencillo de las rutas en cascada y, en lugar de intentar determinar si existen ciclos en realidad, asume lo peor y se niega a crear el acciones referenciales (CASCADE): puede y debe crear las restricciones sin las acciones referenciales. Si no puede modificar su diseño (o hacerlo podría poner en peligro las cosas), debería considerar el uso de desencadenantes como último recurso.

FWIW resolver las rutas en cascada es un problema complejo. Otros productos SQL simplemente ignorarán el problema y le permitirán crear ciclos, en cuyo caso será una carrera ver cuál sobrescribirá el valor al último, probablemente por ignorancia del diseñador (por ejemplo, ACE/Jet lo hace). Entiendo que algunos productos SQL intentarán resolver casos simples. El hecho permanece, SQL Server ni siquiera lo intenta, lo juega de forma ultra segura al no permitir más de una ruta y al menos se lo dice.

+0

llegué a la misma conclution al final, y terminó su solución usando disparadores. Es bueno saber que existe esta diferencia entre DBMS y que, de hecho, es una decisión de diseño en la que estoy atascado. Muchas gracias :) –

+1

una cosa que todavía no puedo entender es que, si este "problema" puede ser resuelto mediante el uso de un disparador, entonces ¿cómo es que un disparador no va a "ciclos causa o múltiples rutas en cascada ..."? – armen

+3

@armen: debido a su disparador suministrará de forma explícita la lógica de que el sistema no podía entender implícitamente a cabo por sí mismo, por ejemplo, si hay varios caminos para una acción de eliminación referencial entonces su código de disparo será definir qué tablas se elimina y en qué orden. – onedaywhen

2

Por sus sonidos tiene una acción OnDelete/OnUpdate en una de sus Llaves extranjeras existentes, que modificará su tabla de códigos.

Así que mediante la creación de esta clave externa, estaría creando un problema cíclico,

P. ej Actualización de los empleados, hace que los códigos a cambiado por una Acción de actualización, hace que los empleados sean cambiados por una Acción de actualización ... etc ...

Si publica sus definiciones de tabla para ambas tablas, & su Clave/restricción externa definiciones que deberíamos poder decirle dónde está el problema ...

+1

Son bastante largas, así que no creo que pueda publicarlas aquí, pero agradecería mucho su ayuda. no sé si hay alguna forma en que pueda enviártelos? malos tratar de describirlo: Las únicas restricciones que existen son de 3 mesas que todos tengan campos que los códigos de referencia por un simple tecla INT Id. El problema parece ser que el empleado tiene varios campos que hacen referencia a la tabla de códigos y que quiero que todos ellos entren en cascada a SET NULL. Todo lo que necesito es que cuando se eliminen los códigos, las referencias a ellos se deben establecer como nulas en todas partes. –

+0

publicarlos todos modos ... No creo que nadie aquí le importa, y la ventana de código a formatear de manera adecuada en un bloque de desplazamiento :) –

11

Me gustaría señalar que (funcionalmente) hay una GRAN diferencia entre los ciclos y/o múltiples rutas en el SCHEMA y los datos. Si bien los ciclos y quizás las multitrayectorias en los DATOS ciertamente podrían complicar el procesamiento y causar problemas de rendimiento (costo de manejo "adecuado"), el costo de estas características en el esquema debería ser cercano a cero.

Dado que la mayoría de los ciclos aparentes en los RDB se producen en las estructuras jerárquicas (organigrama, parte, subparte, etc.) es desafortunado que SQL Server asuma lo peor; es decir, ciclo de esquema == ciclo de datos. De hecho, si usa restricciones de RI, ¡no puede construir un ciclo en los datos!

Sospecho que el problema de multirrutas es similar; es decir, las múltiples rutas en el esquema no necesariamente implican múltiples rutas en los datos, pero tengo menos experiencia con el problema de multitrayecto.

Por supuesto, si SQL Server hizo permitir ciclos, todavía estaría sujeto a una profundidad de 32, pero eso es probablemente adecuado para la mayoría de los casos. (Lástima que no es una configuración de base de datos, sin embargo!)

Los desencadenadores "En lugar de Eliminar" tampoco funcionan. La segunda vez que se visita una tabla, se ignora el desencadenador. Entonces, si realmente quieres simular una cascada, tendrás que usar procedimientos almacenados en presencia de ciclos. Sin embargo, el disparador en lugar de borrar funcionaría para casos de trayectos múltiples.

Celko sugiere una manera "mejor" para representar jerarquías que no introduzca los ciclos, pero hay ventajas y desventajas.

+0

"¡Si está utilizando restricciones de RI, no puede construir un ciclo en los datos!" -- ¡buen punto! – onedaywhen

73

Una situación típica con múltiples rutas de castración será esta: Una tabla maestra con dos detalles, digamos "Maestro" y "Detalle1" y "Detalle2". Ambos detalles son eliminación en cascada. Hasta ahora no hay problemas. Pero, ¿qué pasa si ambos detalles tienen una relación de uno a muchos con alguna otra tabla (digamos "SomeOtherTable")? SomeOtherTable tiene una columna Detail1ID Y una columna Detail2ID.

Master { ID, masterfields } 

Detail1 { ID, MasterID, detail1fields } 

Detail2 { ID, MasterID, detail2fields } 

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields } 

En otras palabras: algunos de los registros en SomeOtherTable están vinculados con Detail1-registros y algunos de los registros en SomeOtherTable están vinculados con los registros detail2. Incluso si se garantiza que los registros SomeOtherTable nunca pertenecen a ambos detalles, ahora es imposible hacer que los registros de SomeOhterTable se eliminen en cascada para ambos detalles, porque hay múltiples rutas en cascada desde Master a SomeOtherTable (una a través del Detail1 y otra vía Detail2). Ahora ya puede haber entendido esto. Aquí es una solución posible:

Master { ID, masterfields } 

DetailMain { ID, MasterID } 

Detail1 { DetailMainID, detail1fields } 

Detail2 { DetailMainID, detail2fields } 

SomeOtherTable {ID, DetailMainID, someothertablefields } 

Todos los campos de ID son clave y campos de incremento automático. La clave está en los campos DetailMainId de las tablas de detalles. Estos campos son clave y referencial referencial. Ahora es posible eliminar todo en cascada eliminando solo registros maestros. La desventaja es que para cada registro de detalle1 Y para cada registro de detalle2, también debe haber un registro detallado de detalle (que en realidad se crea primero para obtener el ID correcto y único).

+0

Su comentario me ayudó mucho a entender el problema que estoy enfrentando. ¡Gracias! Preferiría desactivar la eliminación en cascada para una de las rutas, y luego manejar la eliminación de otros registros de otras maneras (procedimientos almacenados, activadores, por código, etc.). Pero mantengo la solución (agrupación en una ruta) en mente para posibles aplicaciones diferentes del mismo problema ... – freewill

+0

Uno para el uso de la palabra crux (y también para explicar) – masterwok

+0

¿Es esto mejor que escribir desencadenadores? Parece extraño agregar una tabla adicional solo para que la cascada funcione. – dumbledad

1

Esto es porque Emplyee podría tener Colección de otra entidad decir Calificaciones y Calificación podría tener alguna otra colección Universidades p.

public class Employee{ 
public virtual ICollection<Qualification> Qualifications {get;set;} 

}

public class Qualification{ 

public Employee Employee {get;set;} 

public virtual ICollection<University> Universities {get;set;} 

}

public class University{ 

public Qualification Qualification {get;set;} 

}

En DataContext que podría ser como la siguiente

protected override void OnModelCreating(DbModelBuilder modelBuilder){ 

modelBuilder.Entity<Qualification>().HasRequired(x=> x.Employee).WithMany(e => e.Qualifications); 
modelBuilder.Entity<University>.HasRequired(x => x.Qualification).WithMany(e => e.Universities); 

}

en este caso no es la cadena de empleado a la calificación y desde la calificación a las universidades. Entonces me estaba lanzando la misma excepción.

que trabajó para mí cuando cambié

modelBuilder.Entity<Qualification>().**HasRequired**(x=> x.Employee).WithMany(e => e.Qualifications); 

Para

modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications); 
0

Este es un error de base de datos de políticas de activación tipo. Un desencadenador es un código y puede agregar algunas inteligencias o condiciones a una relación de Cascade como Deleción en cascada. Es posible que necesite especializar las opciones de tablas relacionadas de todo esto como Apagar CascadeOnDelete:

protected override void OnModelCreating(DbModelBuilder modelBuilder) 
{ 
    modelBuilder.Entity<TableName>().HasMany(i => i.Member).WithRequired().WillCascadeOnDelete(false); 
} 

O desactivar esta función por completo:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 
0

Trigger es solución para este problema:

IF OBJECT_ID('dbo.fktest2', 'U') IS NOT NULL 
    drop table fktest2 
IF OBJECT_ID('dbo.fktest1', 'U') IS NOT NULL 
    drop table fktest1 
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'fkTest1Trigger' AND type = 'TR') 
    DROP TRIGGER dbo.fkTest1Trigger 
go 
create table fktest1 (id int primary key, anQId int identity) 
go 
    create table fktest2 (id1 int, id2 int, anQId int identity, 
     FOREIGN KEY (id1) REFERENCES fktest1 (id) 
      ON DELETE CASCADE 
      ON UPDATE CASCADE/*,  
     FOREIGN KEY (id2) REFERENCES fktest1 (id) this causes compile error so we have to use triggers 
      ON DELETE CASCADE 
      ON UPDATE CASCADE*/ 
      ) 
go 

CREATE TRIGGER fkTest1Trigger 
ON fkTest1 
AFTER INSERT, UPDATE, DELETE 
AS 
    if @@ROWCOUNT = 0 
     return 
    set nocount on 

    -- This code is replacement for foreign key cascade (auto update of field in destination table when its referenced primary key in source table changes. 
    -- Compiler complains only when you use multiple cascased. It throws this compile error: 
    -- Rrigger Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, 
    -- or modify other FOREIGN KEY constraints. 
    IF ((UPDATE (id) and exists(select 1 from fktest1 A join deleted B on B.anqid = A.anqid where B.id <> A.id))) 
    begin  
     update fktest2 set id2 = i.id 
      from deleted d 
      join fktest2 on d.id = fktest2.id2 
      join inserted i on i.anqid = d.anqid   
    end   
    if exists (select 1 from deleted)  
     DELETE one FROM fktest2 one LEFT JOIN fktest1 two ON two.id = one.id2 where two.id is null -- drop all from dest table which are not in source table 
GO 

insert into fktest1 (id) values (1) 
insert into fktest1 (id) values (2) 
insert into fktest1 (id) values (3) 

insert into fktest2 (id1, id2) values (1,1) 
insert into fktest2 (id1, id2) values (2,2) 
insert into fktest2 (id1, id2) values (1,3) 

select * from fktest1 
select * from fktest2 

update fktest1 set id=11 where id=1 
update fktest1 set id=22 where id=2 
update fktest1 set id=33 where id=3 
delete from fktest1 where id > 22 

select * from fktest1 
select * from fktest2 
0

Mi solución a este problema que se encuentra usando ASP.NET Core 2,0 y EF Core 2.0 fue para realizar el siguiente en orden:

  1. comando Ejecutar update-database en paquete Management Console (PMC) para crear la base de datos (esto se traduce en la "Introducción a los contras FOREIGN KEY Traint ... puede causar ciclos o múltiples rutas en cascada." error)

  2. comando Ejecutar script-migration -Idempotent en PMC para crear una secuencia de comandos que se pueden ejecutar independientemente de las tablas existentes/restricciones

  3. Tome el guión resultante y ON DELETE CASCADE encontrar y reemplazar con ON DELETE NO ACTION

  4. Ejecutar el SQL modificado la base de datos

Ahora, sus migraciones Shou Debe estar actualizado y las eliminaciones en cascada no deben ocurrir.

Lástima que no era capaz de encontrar alguna manera de hacer esto en Entity Framework Core 2.0.

¡Buena suerte!

Cuestiones relacionadas