外键约束可能会导致循环或多个级联path?
当我尝试添加约束到我的表时,我有一个问题。 我得到的错误:
在表'Employee'中引入FOREIGN KEY约束'FK74988DB24B3C886'可能会导致循环或多个级联path。 指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。
我的约束是在Code
表和employee
表之间。 Code
表包含Id
, Name
, FriendlyName
, Type
和Value
。 employee
有许多参考代码的字段,以便可以为每种types的代码提供参考。
如果被引用的代码被删除,我需要将字段设置为null。
任何想法,我可以做到这一点?
SQL Server对级联path进行简单的计算,而不是试图确定是否有任何周期实际存在,它假设最差并拒绝创build引用操作(CASCADE):您可以并且应该在没有引用操作的情况下创build约束。 如果你不能改变你的devise(或这样做会损害事情),那么你应该考虑使用触发器作为最后的手段。
FWIW解决级联path是一个复杂的问题。 其他SQL产品会简单地忽略这个问题,并允许你创build循环,在这种情况下,看看哪个会覆盖最后一个值,可能是由于devise者的无知(比如ACE / Jet这样做)。 我了解一些SQL产品将尝试解决简单的情况。 事实依然,SQL Server甚至不尝试,通过禁止多条path来实现超安全,至less它告诉你这么做。
一个典型的情况下,多个cascasingpath将是这样的:一个主表有两个细节,比如说“主”,“细节1”和“细节2”。 两个细节都是级联删除。 到目前为止没有问题。 但是,如果两个细节与其他表(如“SomeOtherTable”)具有一对多的关系呢? SomeOtherTable具有Detail1ID列和Detail2ID列。
Master { ID, masterfields } Detail1 { ID, MasterID, detail1fields } Detail2 { ID, MasterID, detail2fields } SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }
换句话说:SomeOtherTable中的某些logging与Detail1-logging链接,SomeOtherTable中的某些logging与Detail2logging链接。 即使保证SomeOtherTablelogging不属于这两个细节,现在也不可能使SomeOhterTable的logging级联删除这两个细节,因为从Master到SomeOtherTable(一个通过Detail1,一个通过Detail2)有多个级联path。 现在你可能已经明白了这一点。 这是一个可能的解决scheme:
Master { ID, masterfields } DetailMain { ID, MasterID } Detail1 { DetailMainID, detail1fields } Detail2 { DetailMainID, detail2fields } SomeOtherTable {ID, DetailMainID, someothertablefields }
所有ID字段都是关键字段并自动递增。 症结在于Detail表格的DetailMainId字段。 这些领域都是关键和参考性的限制。 现在可以通过只删除主logging来级联删除所有内容。 缺点是对于每个detail2logging的每个detail1loggingAND,还必须有一个DetailMainlogging(实际上是首先创build的,以获得正确和唯一的id)。
我会指出(在function上)SCHEMA和DATA中的循环和/或多个path之间有很大差异。 虽然DATA中的周期和可能的多path可能肯定会使处理复杂化,并导致性能问题(“恰当”处理的成本),但这些特征在模式中的成本应接近于零。
由于RDB中的大多数表观循环出现在层次结构(组织结构图,部分,子部分等)中,所以不幸的是SQL Server假设为最差; 即模式循环==数据循环。 事实上,如果你使用RI约束,你实际上不能在数据中build立一个循环!
我怀疑多path问题是相似的; 即模式中的多条path不一定意味着数据中有多条path,但我对多path问题的经验不足。
当然,如果SQL Server 确实允许循环,它仍然会受到32的深度,但是这在大多数情况下可能是足够的。 (太糟糕了,这不是数据库设置!)
“而不是删除”触发器也不起作用。 第二次访问表时,触发器被忽略。 所以,如果你真的想要模拟一个级联,你将不得不在循环中使用存储过程。 然而,删除触发器将适用于多path情况。
Celko提出了一个“更好”的方法来表示不引入循环的层次结构,但是存在权衡。
有一篇文章解释了如何使用触发器执行多个删除path。 也许这对于复杂的场景很有用。
通过它的声音,你有一个OnDelete / OnUpdate行动在你现有的外键之一,这将修改你的代码表。
所以通过创build这个外键,你会创build一个循环的问题,
例如,更新员工,导致代码更改通过更新操作,导致员工通过更新操作更改…等…
如果你发布你的表定义这两个表,&你的外键/约束定义,我们应该能够告诉你问题在哪里…
这是因为Emplyee可能会收集其他实体说资格和资格可能有一些其他收集大学如
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;}
}
在DataContext上它可能如下所示
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);
}
在这种情况下,从员工到资格链,从资格到大学链。 所以这也是抛出同样的例外给我。
当我改变时,它对我有效
modelBuilder.Entity<Qualification>().**HasRequired**(x=> x.Employee).WithMany(e => e.Qualifications);
至
modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications);
这是types数据库触发器策略的错误。 触发器是代码,可以将一些智能或条件添加到Cascade删除等Cascade关系中。 您可能需要专门化相关的表格选项,例如closuresCascadeOnDelete :
protected override void OnModelCreating( DbModelBuilder modelBuilder ) { modelBuilder.Entity<TableName>().HasMany(i => i.Member).WithRequired().WillCascadeOnDelete(false); }
或完全closures此function:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
触发器是解决这个问题的方法:
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