外键约束可能导致循环或多条级联路径?

我有一个问题,当我试图添加约束到我的表。我得到了错误:

在表'Employee'上引入外键约束'FK74988DB24B3C886'可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他外键约束。

我的约束是在Code表和employee表之间。Code表包含IdNameFriendlyNameTypeValueemployee有许多引用代码的字段,因此每种类型的代码都有一个引用。

我需要字段被设置为空,如果引用的代码被删除。

你知道我该怎么做吗?

162183 次浏览

听起来,你有一个OnDelete/OnUpdate动作在你现有的外键之一,这将修改你的代码表。

通过创建这个外键,你会创建一个循环问题,

例如,更新Employees,导致代码被一个On Update Action改变,导致Employees被一个On Update Action改变…等等……

如果你发布了两个表的表定义,&你的外键/约束定义,我们应该能够告诉你问题在哪里…

SQL Server对级联路径进行简单的计数,而不是试图计算是否存在任何循环,它假设最坏的情况并拒绝创建引用操作(cascade):您可以并且应该仍然在没有引用操作的情况下创建约束。如果你不能改变你的设计(或者这样做会损害一些东西),那么你应该考虑使用触发器作为最后的手段。

FWIW求解级联路径是一个复杂的问题。其他SQL产品将简单地忽略这个问题,并允许您创建循环,在这种情况下,它将是一场比赛,看看谁将最后覆盖的值,可能是设计师的无知(例如ACE/Jet这样做)。我知道一些SQL产品将尝试解决简单的情况。事实是,SQL Server甚至没有尝试,它通过禁止多个路径来保持超级安全,至少它会告诉你。

微软自己建议使用触发器代替FK约束。

我想指出的是(从功能上)SCHEMA和DATA中的循环和/或多条路径之间有很大的区别。虽然DATA中的循环和多路径肯定会使处理复杂化并导致性能问题(“适当”处理的成本),但模式中这些特征的成本应该接近于零。

由于rdb中大多数明显的循环发生在层次结构中(组织结构图、部分、子部分等),不幸的是SQL Server假设了最坏的情况;即,模式周期==数据周期。事实上,如果您使用RI约束,您实际上无法在数据中构建一个循环!

我认为多路径问题与此类似;也就是说,模式中的多条路径并不一定意味着数据中的多条路径,但我对多路径问题的经验较少。

当然,如果SQL Server 做了允许循环,它的深度仍然是32,但这对于大多数情况可能已经足够了。(可惜这不是一个数据库设置!)

“代替删除”触发器也不起作用。第二次访问表时,触发器将被忽略。所以,如果你真的想要模拟级联,你就必须在存在循环的情况下使用存储过程。但是,Instead-of-Delete-Trigger适用于多路径情况。

Celko提出了一种“更好的”表示层次结构的方法,这种方法不引入循环,但也有折衷之处。

多个套管路径的典型情况如下: 一个有两个细节的主表,我们说“master”和“Detail1”和“Detail2”。这两个细节都是级联删除。到目前为止还没有问题。但是,如果两个细节都与其他表(比如“SomeOtherTable”)有一对多的关系呢?SomeOtherTable有一个Detail1ID-column和一个Detail2ID-column。< / p >
Master { ID, masterfields }


Detail1 { ID, MasterID, detail1fields }


Detail2 { ID, MasterID, detail2fields }


SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }
换句话说:SomeOtherTable中的一些记录链接到detail1记录,SomeOtherTable中的一些记录链接到Detail2记录。即使保证SomeOtherTable记录永远不属于两个细节,现在也不可能使SomeOtherTable的记录级联删除两个细节,因为从Master到SomeOtherTable有多条级联路径(一条通过Detail1,另一条通过Detail2)。 现在你可能已经明白了。这里有一个可能的解决方案:

Master { ID, masterfields }


DetailMain { ID, MasterID }


Detail1 { DetailMainID, detail1fields }


Detail2 { DetailMainID, detail2fields }


SomeOtherTable {ID, DetailMainID, someothertablefields }

所有ID字段都是键字段和自动递增字段。问题的关键在于Detail表的DetailMainId字段。这些字段既是关键约束,又是参考约束。现在可以通过只删除主记录来级联删除所有内容。缺点是,对于每个detail1记录和每个detail2记录,还必须有一个detailmain记录(实际上是首先创建该记录以获得正确和唯一的id)。

这是因为员工可能有其他实体的集合,比如资格和资格可能有一些其他的集合大学 例如< / 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;}

在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);

这是数据库触发策略类型的错误。触发器是代码,可以向级联关系(如级联删除)添加一些智能或条件。你可能需要专门处理相关的表选项,比如关闭CascadeOnDelete:

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

或者完全关闭此功能:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

有一篇文章解释了如何使用触发器执行多个删除路径。也许这对于复杂的场景很有用。

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

Trigger是这个问题的解决方案:

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

我用ASP解决了这个问题。NET Core 2.0和EF Core 2.0是按以下顺序执行的:

  1. 在包管理控制台(PMC)中运行update-database命令来创建数据库(这将导致“引入外键约束…可能导致循环或多个级联路径。"错误)

  2. 在PMC中运行script-migration -Idempotent命令来创建一个可以运行的脚本,不管现有的表/约束

  3. 使用结果脚本并找到ON DELETE CASCADE并将其替换为ON DELETE NO ACTION

  4. 对数据库执行修改后的SQL

现在,您的迁移应该是最新的,级联删除应该不会发生。

太糟糕了,我在实体框架核心2.0中找不到任何方法来做到这一点。

好运!

一些数据库,尤其是SQL Server,对形成循环的级联行为有限制。 有两种方法可以处理这种情况: 1.将一个或多个关系更改为不级联删除。 2.配置数据库时不要使用一个或多个级联删除,然后确保加载所有相关实体,以便EF Core可以执行级联行为。 请参考此链接:
数据库级联限制 < / p >

大量数据库更新来抵消pk:做一个数据库副本代替。

特殊用例:A公司使用与b公司具有相同模式的数据库,因为他们已经合并,所以他们希望使用单个数据库。因此,来自公司B数据库的许多表的主键必须偏移,以避免与公司A的记录发生冲突。

一种解决方案是将外键定义为ON UPDATE CASCADE,并抵消带有外键的主键。但是如果你这样做的话会有很多障碍(味精1785,味精8102,…)。

因此,我想到的一个更好的主意是简单地复制数据库,DROP和re CREATE必须有其PKs|FKs偏移量的表,并复制数据(在这样做的同时,偏移主键和外键)。

避免所有的麻烦。