Foreign key constraint may cause cycles or multiple cascade paths?
当我尝试向表格添加约束时,我遇到了问题。 我收到错误:
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.
我的约束是在
如果删除引用的代码,我需要将字段设置为null。
我有什么想法可以做到这一点?
SQL Server对级联路径进行简单计数,而不是试图确定是否存在任何周期,它假设最坏的并且拒绝创建引用操作(CASCADE):您可以而且应该仍然创建没有引用操作的约束。如果你不能改变你的设计(或者这样做会损害你的设计)那么你应该考虑使用触发器作为最后的手段。
FWIW解析级联路径是一个复杂的问题。其他SQL产品将简单地忽略该问题并允许您创建循环,在这种情况下,它将是一个竞争,看看哪个将覆盖最后的值,可能是由于设计者的无知(例如ACE / Jet这样做)。我理解一些SQL产品将尝试解决简单的情况。事实仍然是,SQL Server甚至没有尝试,通过禁止多条路径来播放它是非常安全的,至少它会告诉你。
微软自己建议使用触发器而不是FK约束。
具有多个级联路径的典型情况是:
一个包含两个细节的主表,让我们说"Master"和"Detail1"和"Detail2"。这两个细节都是级联删除。到目前为止没有问题。但是,如果两个细节与其他一些表(例如"SomeOtherTable")具有一对多的关系,那该怎么办呢? SomeOtherTable具有Detail1ID列和Detail2ID列。
1 2 3 4 5 6 7 | Master { ID, masterfields } Detail1 { ID, MasterID, detail1fields } Detail2 { ID, MasterID, detail2fields } SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields } |
换句话说:SomeOtherTable中的一些记录与Detail1-records链接,SomeOtherTable中的一些记录与Detail2记录链接。即使保证SomeOtherTable记录永远不属于两个细节,现在也不可能为这两个细节制作SomeOhterTable的记录级联删除,因为从Master到SomeOtherTable有多个级联路径(一个通过Detail1,一个通过Detail2)。
现在你可能已经理解了这一点。这是一个可能的解决方案:
1 2 3 4 5 6 7 8 9 | Master { ID, masterfields } DetailMain { ID, MasterID } Detail1 { DetailMainID, detail1fields } Detail2 { DetailMainID, detail2fields } SomeOtherTable {ID, DetailMainID, someothertablefields } |
所有ID字段都是关键字段和自动递增。关键在于Detail表的DetailMainId字段。这些字段既是关键的,也是参考的约束。现在可以通过仅删除主记录来级联删除所有内容。缺点是对于每个detail1-record和每个detail2记录,还必须有一个DetailMain记录(实际上是先创建它以获得正确且唯一的id)。
我会指出(功能上)在SCHEMA和DATA中循环和/或多个路径之间存在巨大差异。虽然DATA中的循环和可能的多路径肯定会使处理变得复杂并导致性能问题("正确"处理的成本),但架构中这些特征的成本应该接近于零。
由于RDB中的大多数明显周期都出现在层次结构(组织结构图,部分,子部分等)中,因此不幸的是SQL Server假设最糟糕;即,模式周期==数据周期。实际上,如果您使用RI约束,则无法在数据中实际构建循环!
我怀疑多路径问题是类似的;即,模式中的多个路径不一定意味着数据中的多个路径,但我对多路径问题的经验较少。
当然,如果SQL Server确实允许循环,它仍然会受到32的深度限制,但这对大多数情况来说可能已经足够了。 (太糟糕了,但这不是数据库设置!)
"而不是删除"触发器也不起作用。第二次访问表时,将忽略触发器。因此,如果您真的想要模拟级联,则必须在存在循环的情况下使用存储过程。但是,替代删除触发器适用于多路径情况。
Celko提出了一种"更好"的方式来表示不引入周期的层次结构,但存在权衡。
有一篇文章介绍了如何使用触发器执行多个删除路径。也许这对复杂场景很有用。
http://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger/
通过它的声音,您在一个现有的外键上有一个OnDelete / OnUpdate操作,它将修改您的代码表。
因此,通过创建此外键,您将创建一个循环问题,
例如。更新员工,导致代码由On Update Action更改,导致Employees被On Update Action更改......等等...
如果您为两个表发布表定义,&你的外键/约束定义,我们应该能够告诉你问题在哪里......
触发器是解决此问题的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | 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 |
这是因为Emplyee可能有其他实体的集合说资格和资格可能有一些其他集合大学
例如
1 2 | public class Employee{ public virtual ICollection<Qualification> Qualifications {GET;SET;} |
}
1 2 3 4 5 | public class Qualification{ public Employee Employee {GET;SET;} public virtual ICollection<University> Universities {GET;SET;} |
}
1 2 3 | public class University{ public Qualification Qualification {GET;SET;} |
}
在DataContext上,它可能如下所示
1 2 3 4 | 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); |
}
在这种情况下,从员工到资格,从资格到大学都有链条。所以它给我带来了同样的例外。
当我改变时,它对我有用
1 | modelBuilder.Entity<Qualification>().**HasRequired**(x=> x.Employee).WithMany(e => e.Qualifications); |
至
1 | modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications); |
这是类型数据库触发器策略的错误。触发器是代码,可以为Cascade删除等级联关系添加一些智能或条件。您可能需要专门设置相关的表选项,例如关闭CascadeOnDelete:
1 2 3 4 | protected override void OnModelCreating( DbModelBuilder modelBuilder ) { modelBuilder.Entity<TableName>().HasMany(i => i.Member).WithRequired().WillCascadeOnDelete(FALSE); } |
或者完全关闭此功能:
1 | modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); |
使用ASP.NET Core 2.0和EF Core 2.0遇到此问题的解决方案是按顺序执行以下操作:
在程序包管理控制台(PMC)中运行
在PMC中运行
获取生成的脚本并找到
对数据库执行修改后的SQL
现在,您的迁移应该是最新的,并且不应发生级联删除。
太糟糕了,我无法在Entity Framework Core 2.0中找到任何方法。
祝好运!