Restrição de chave estrangeira pode causar ciclos ou vários caminhos em cascata?

Eu tenho um problema quando tento adicionar restrições às minhas tabelas. Eu recebo o erro:

A introdução da restrição FOREIGN KEY ‘FK74988DB24B3C886’ na tabela ‘Employee’ pode causar ciclos ou vários caminhos em cascata. Especifique ON DELETE NO ACTION ou ON UPDATE NO ACTION, ou modifique outras restrições FOREIGN KEY.

Minha restrição está entre uma tabela de Code e uma tabela de employee . A tabela de Code contém Id , Name , FriendlyName , Type e um Value . O employee possui um número de campos que referenciam códigos, para que possa haver uma referência para cada tipo de código.

Eu preciso que os campos sejam definidos como null se o código que é referenciado for excluído.

Alguma idéia de como posso fazer isso?

O SQL Server faz contagem simples de caminhos em cascata e, em vez de tentar descobrir se algum ciclo realmente existe, ele assume o pior e se recusa a criar as ações referenciais (CASCADE): você pode e deve ainda criar as restrições sem as ações referenciais. Se você não pode alterar o seu design (ou fazer isso comprometer as coisas), então você deve considerar o uso de gatilhos como um último recurso.

FWIW resolver caminhos em cascata é um problema complexo. Outros produtos SQL irão simplesmente ignorar o problema e permitir que você crie ciclos, caso em que será uma corrida para ver qual replaceá o valor por último, provavelmente pela ignorância do designer (por exemplo, o ACE / Jet faz isso). Eu entendo que alguns produtos SQL tentarão resolver casos simples. Fato permanece, o SQL Server nem sequer tenta, joga ultra seguro, não permitindo mais de um caminho e, pelo menos, diz-lhe isso.

Uma situação típica com vários caminhos de cascalhos será esta: Uma tabela mestre com dois detalhes, digamos “Mestre” e “Detalhe1” e “Detalhe2”. Ambos os detalhes são em cascata. Até agora sem problemas. Mas e se ambos os detalhes tiverem uma relação de um para muitos com alguma outra tabela (diga “SomeOtherTable”). SomeOtherTable tem uma coluna Detail1ID e uma coluna Detail2ID.

 Master { ID, masterfields } Detail1 { ID, MasterID, detail1fields } Detail2 { ID, MasterID, detail2fields } SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields } 

Em outras palavras: alguns dos registros em SomeOtherTable são vinculados a registros Detail1 e alguns dos registros em SomeOtherTable são vinculados a registros Detail2. Mesmo se for garantido que SomeOtherTable-records nunca pertencem a ambos os Detalhes, agora é impossível fazer os registros de SomeOhterTable em cascata para ambos os detalhes, porque há vários caminhos em cascata de Mestre para SomeOtherTable (um via Detail1 e um via Detail2). Agora você já deve ter entendido isso. Aqui está uma solução possível:

 Master { ID, masterfields } DetailMain { ID, MasterID } Detail1 { DetailMainID, detail1fields } Detail2 { DetailMainID, detail2fields } SomeOtherTable {ID, DetailMainID, someothertablefields } 

Todos os campos de ID são campos-chave e incremento automático. O ponto crucial está nos campos DetailMainId das tabelas Detail. Esses campos são fundamentais e de referência. Agora é possível fazer cascata para excluir tudo excluindo apenas os registros mestre. A desvantagem é que para cada registro detail1-record AND para cada detail2, também deve haver um registro DetailMain (que é realmente criado primeiro para obter o id correto e único).

Gostaria de salientar que (funcionalmente) há uma GRANDE diferença entre ciclos e / ou múltiplos caminhos no SCHEMA e no DATA. Embora os ciclos e talvez os multipaths nos DATA possam certamente complicar o processamento e causar problemas de desempenho (custo de manipulação “adequada”), o custo dessas características no esquema deve ser próximo de zero.

Como a maioria dos ciclos aparentes em RDBs ocorrem em estruturas hierárquicas (organograma, parte, subparte, etc.), é lamentável que o SQL Server assuma o pior; isto é, ciclo de esquema == ciclo de dados. Na verdade, se você estiver usando restrições de RI, não poderá realmente criar um ciclo nos dados!

Eu suspeito que o problema multipath é similar; Ou seja, vários caminhos no esquema não implicam necessariamente em múltiplos caminhos nos dados, mas eu tenho menos experiência com o problema do multipath.

É claro que se o SQL Server permitisse ciclos, ainda estaria sujeito a uma profundidade de 32, mas isso provavelmente é adequado para a maioria dos casos. (Pena que não é uma configuração de database no entanto!)

Os gatilhos “Em vez de Excluir” também não funcionam. Na segunda vez que uma tabela é visitada, o gatilho é ignorado. Então, se você realmente quiser simular uma cascata, terá que usar stored procedures na presença de ciclos. O Instead-of-Delete-Trigger funcionaria para casos de multipath.

Celko sugere uma maneira “melhor” de representar hierarquias que não introduzem ciclos, mas há compensações.

Há um artigo disponível no qual explica como executar vários caminhos de exclusão usando gatilhos. Talvez isso seja útil para cenários complexos.

http://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger/

Pelos sons, você tem uma ação OnDelete / OnUpdate em uma de suas Chaves Estrangeiras existentes, que modificará sua tabela de códigos.

Então, criando essa chave estrangeira, você estaria criando um problema cíclico,

Por exemplo, Atualizar funcionários, faz com que códigos sejam alterados por uma ação de atualização, faz com que os funcionários sejam alterados por uma ação de atualização … etc …

Se você postar suas Definições de tabela para ambas as tabelas, e suas definições de Chave estrangeira / restrição, poderemos dizer a você onde está o problema …

Isso ocorre porque o Emplyee pode ter uma coleção de outra entidade dizendo que as Qualificações e Qualificações podem ter outras Universidades de coleta, por exemplo.

 public class Employee{ public virtual ICollection Qualifications {get;set;} 

}

 public class Qualification{ public Employee Employee {get;set;} public virtual ICollection Universities {get;set;} 

}

 public class University{ public Qualification Qualification {get;set;} 

}

No DataContext, pode ser como abaixo

 protected override void OnModelCreating(DbModelBuilder modelBuilder){ modelBuilder.Entity().HasRequired(x=> x.Employee).WithMany(e => e.Qualifications); modelBuilder.Entity.HasRequired(x => x.Qualification).WithMany(e => e.Universities); 

}

Nesse caso, há uma cadeia de funcionários para qualificação e de qualificação para universidades. Então estava jogando a mesma exceção para mim.

Funcionou para mim quando eu mudei

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

Para

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

Este é um erro de políticas de acionador de database de tipo. Um gatilho é um código e pode adicionar algumas inteligências ou condições a uma relação em Cascata, como a Exclusão em Cascata. Talvez seja necessário especializar as opções de tabelas relacionadas ao redor disso, como Desativar o CascadeOnDelete :

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

Ou desligue este recurso completamente:

 modelBuilder.Conventions.Remove(); 

Trigger é a solução 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 

Minha solução para esse problema encontrado usando o ASP.NET Core 2.0 e o EF Core 2.0 foi executar o seguinte na ordem:

  1. Execute o comando update-database no Management Console Console (PMC) para criar o database (isso resulta no erro “Introducing FOREIGN KEY constraint … pode causar ciclos ou vários caminhos em cascata”.)

  2. Execute o comando script-migration -Idempotent no PMC para criar um script que possa ser executado independentemente das tabelas / restrições existentes

  3. Pegue o script resultante e encontre ON DELETE CASCADE e substitua por ON DELETE NO ACTION

  4. Execute o SQL modificado no database

Agora, suas migrações devem estar atualizadas e as exclusões em cascata não devem ocorrer.

Pena que não consegui encontrar nenhuma maneira de fazer isso no Entity Framework Core 2.0.

Boa sorte!