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

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

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

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

你知道我该怎么做吗?


当前回答

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

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

微软自己建议使用触发器而不是FK约束。

其他回答

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

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

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

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

微软自己建议使用触发器而不是FK约束。

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

在包管理控制台(PMC)中运行update-database命令来创建数据库(这将导致“引入外键约束…”可能导致循环或多个级联路径。"错误) 在PMC中运行script-migration -Idempotent命令,创建一个可以不受现有表/约束而运行的脚本 使用结果脚本并找到ON DELETE CASCADE并替换为ON DELETE NO ACTION 对数据库执行修改后的SQL

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

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

好运!

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

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

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

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

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

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

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