197

当我尝试向我的表添加约束时遇到问题。我得到错误:

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

我的约束是在一个Code表和一个employee表之间。该Code表包含IdNameFriendlyNameTypea Value。有许多引用代码的employee字段,因此可以对每种类型的代码进行引用。

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

任何想法我怎么能做到这一点?

4

11 回答 11

198

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

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

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

于 2009-05-12T10:03:56.847 回答
117

具有多个级联路径的典型情况是这样的:具有两个详细信息的主表,例如“Master”和“Detail1”和“Detail2”。两个细节都是级联删除。到目前为止没有问题。但是,如果这两个细节都与某个其他表(例如“SomeOtherTable”)具有一对多的关系怎么办。SomeOtherTable 有一个 Detail1ID 列和一个 Detail2ID 列。

Master { ID, masterfields }

Detail1 { ID, MasterID, detail1fields }

Detail2 { ID, MasterID, detail2fields }

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }

换句话说:SomeOtherTable 中的一些记录与 Detail1-records 相关联,SomeOtherTable 中的一些记录与 Detail2 记录相关联。即使保证 SomeOtherTable-records 永远不属于这两个 Details,现在也不可能对两个 Details 进行 SomeOhterTable 的记录级联删除,因为从 Master 到 SomeOtherTable 有多个级联路径(一个通过 Detail1,一个通过 Detail2)。现在你可能已经明白了。这是一个可能的解决方案:

Master { ID, masterfields }

DetailMain { ID, MasterID }

Detail1 { DetailMainID, detail1fields }

Detail2 { DetailMainID, detail2fields }

SomeOtherTable {ID, DetailMainID, someothertablefields }

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

于 2010-08-23T13:55:17.177 回答
14

我会指出(功能上)SCHEMA 和 DATA 中的周期和/或多条路径之间存在很大差异。虽然 DATA 中的循环和多路径肯定会使处理复杂化并导致性能问题(“正确”处理的成本),但模式中这些特征的成本应该接近于零。

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

我怀疑多路径问题是相似的;即,模式中的多个路径不一定意味着数据中的多个路径,但我对多路径问题的经验较少。

当然,如果 SQL Server确实允许循环,它仍然会受到 32 深度的影响,但这对于大多数情况来说可能已经足够了。(太糟糕了,这不是数据库设置!)

“代替删除”触发器也不起作用。第二次访问表时,将忽略触发器。因此,如果您真的想模拟级联,则必须在存在循环的情况下使用存储过程。然而,替代删除触发器将适用于多路径情况。

Celko 提出了一种“更好”的方式来表示不引入循环的层次结构,但需要权衡取舍。

于 2010-05-04T19:29:02.167 回答
8

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

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

于 2015-05-28T14:38:50.837 回答
3

通过它的声音,您在现有外键之一上有一个 OnDelete/OnUpdate 操作,这将修改您的代码表。

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

例如,更新员工,导致代码被更新操作更改,导致员工被更新操作更改......等等......

如果您发布两个表的表定义和外键/约束定义,我们应该能够告诉您问题出在哪里......

于 2009-05-12T07:52:03.457 回答
2

这是因为员工可能有其他实体的集合,比如资格和资格可能有一些其他集合大学,例如

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);
于 2013-12-16T16:58:24.483 回答
1

触发器是这个问题的解决方案:

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
于 2015-12-03T14:18:10.357 回答
0

这是数据库触发策略类型的错误。触发器是代码,可以将一些智能或条件添加到级联关系中,例如级联删除。您可能需要专门针对此相关的表选项,例如Turning off CascadeOnDelete

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

或完全关闭此功能:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
于 2014-10-10T11:35:32.533 回答
0

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

于 2021-08-24T17:51:55.903 回答
0

大规模数据库更新以抵消 PK:改为制作数据库副本。

特殊用例:公司 A 使用与公司 B 具有相同架构的数据库。因为他们已经合并,他们想使用单个数据库。因此,B公司数据库中的许多表必须有它们的主键偏移,以避免与A公司的记录发生冲突。

一种解决方案可能是将外键定义为 ON UPDATE CASCADE,并偏移具有外键的主键。但是如果你这样做,会有很多障碍(Msg 1785,Msg 8102,...)。

所以我想到的一个更好的主意是简单地制作数据库的副本,DROP 并重新创建必须具有 PKs|FKs 偏移量的表,然后复制数据(在这样做的同时,偏移主键和外键键)。

避免所有的麻烦。

于 2021-10-15T10:38:34.703 回答
-2

我使用 ASP.NET Core 2.0 和 EF Core 2.0 遇到的这个问题的解决方案是按顺序执行以下操作:

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

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

  3. 获取生成的脚本并查找ON DELETE CASCADE并替换为ON DELETE NO ACTION

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

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

太糟糕了,我无法在 Entity Framework Core 2.0 中找到任何方法来做到这一点。

祝你好运!

于 2017-11-28T20:37:46.267 回答