尝试向表中添加约束时出现问题。我收到错误:


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


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

如果删除了所引用的代码,则需要将这些字段设置为null 。

任何想法我该怎么做?

评论

解决方案之一在这里

#1 楼

SQL Server会简单地计算级联路径,而不是尝试找出是否存在任何实际的循环,而是假定最坏的情况并拒绝创建引用动作(CASCADE):您可以并且应该仍然创建没有引用动作的约束。如果您不能更改设计(或这样做会损害性能),那么您应该考虑使用触发器作为最后的选择。

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

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

评论


我仍然无法理解的一件事是,如果可以通过使用触发器来解决此“问题”,那么触发器为什么不会“导致循环或多个级联路径...”呢?

–armen
2014年7月24日在10:17

@armen:因为您的触发器将显式提供系统无法自行隐式找出的逻辑,例如,如果存在删除引用动作的多个路径,则您的触发器代码将定义要删除的表和顺序。

–有一天
2014年8月5日15:22

并且触发器在第一个操作完成后执行,因此不会发生比赛。

– Bon
15年8月4日在16:50

@dumbledad:我的意思是,仅在约束(可能是组合条件)无法完成工作时才使用触发器。约束是声明性的,其实现是系统的责任。触发器是过程代码,您必须对实现进行编码(和调试),并忍受其缺点(性能更差等)。

–有一天
16-2-22在15:14

这样做的问题是,只有删除外键约束后,触发器才起作用,这意味着您对数据库插入没有参照完整性检查,因此需要更多的触发器来处理它。触发解决方案是一个导致简明数据库设计的难题。

–中微子
2月20日下午16:29

#2 楼

具有多个级联路径的典型情况是:
具有两个详细信息的主表,比如说“ 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-记录链接,而SomeOtherTable中的某些记录与Detail2记录链接。即使可以保证SomeOtherTable记录永远都不会属于这两个明细,但现在也无法对这两个明细进行SomeOhterTable的记录级联删除,因为从Master到SomeOtherTable有多个级联路径(一个通过Detail1,一个通过Detail2)。 br />现在您可能已经了解了。这是一个可能的解决方案:

Master { ID, masterfields }

DetailMain { ID, MasterID }

Detail1 { DetailMainID, detail1fields }

Detail2 { DetailMainID, detail2fields }

SomeOtherTable {ID, DetailMainID, someothertablefields }


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

评论


您的评论对我了解我所面临的问题大有帮助。谢谢!我宁愿关闭路径之一的级联删除,然后以其他方式(存储过程,触发器,通过代码等)处理其他记录的删除。但是对于同一个问题的可能不同应用,我会牢记您的解决方案(按一条路径分组)...

–自由意志
14年2月14日在21:39

一个使用“紧要关头”一词(也用于解释)

– masterwok
2014年12月11日下午4:35

这比编写触发器更好吗?为了使级联工作而添加一个额外的表似乎很奇怪。

– dumbledad
16年2月2日在10:04

任何事情都比编写触发器更好。他们的逻辑是不透明的,与其他任何事物相比,它们的效率都不高。将较大的表分成较小的表以进行更好的控制,这只是更好的规范化数据库的自然结果,而本身并不是要关注的事情。

–中微子
2月20日在16:38



#3 楼

我要指出的是(在功能上)SCHEMA和DATA中的循环和/或多个路径之间存在很大的差异。虽然DATA中的周期甚至多路径肯定会导致处理复杂化并导致性能问题(“适当”处理的代价),但架构中这些特征的代价应接近于零。

由于RDB中大多数明显的循环都发生在层次结构(组织结构图,零件,子零件等)中,因此不幸的是SQL Server假定情况最糟。即模式周期==数据周期。实际上,如果您使用的是RI约束,则实际上无法在数据中构建循环!

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

当然,如果SQL Server确实允许循环,则仍然会受到影响深度为32,但对于大多数情况来说可能就足够了。 (但是很遗憾,这不是数据库设置!)

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

Celko建议采用一种“更好”的方式来表示层次结构,该方法不会引入循环,但需要权衡取舍。

评论


“如果使用RI约束,则实际上无法在数据中建立循环!” - 好点子!

–有一天
16 Mar 23 '16 at 17:11

当然,您可以建立数据循环性,但是对于MSSQL,只能使用UPDATE。其他RDBM支持延迟的约束(在提交时确保完整性,而不是在插入/更新/删除时确保完整性)。

–卡尔·克里格(Carl Krig)
19年6月13日在15:51

#4 楼

有一篇文章介绍了如何使用触发器执行多个删除路径。也许这对复杂的情况很有用。

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

#5 楼

听起来,您对一个现有外键具有OnDelete / OnUpdate操作,这将修改您的代码表。

因此,通过创建此外键,您将创建一个循环问题,

例如更新员工,导致通过“更新操作”更改代码,导致员工通过“更新操作”更改...等等...

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

评论


它们相当长,所以我不认为可以在此处发布它们,但是非常感谢您的帮助-不知道我是否可以通过某些方式将它们发送给您?我会尝试对其进行描述:存在的唯一约束来自3个表,这些表都具有通过简单的INT Id键引用代码的字段。问题似乎是Employee有几个引用代码表的字段,我希望它们全部都级联为SET NULL。我所需要的是,删除代码后,到处都应将对它们的引用设置为null。

–克里斯蒂安·尼尔森(Christian Nielsen)
09年5月12日在8:13

无论如何都发布它们...我想这里的任何人都不会介意,并且代码窗口会在滚动块中正确格式化它们:)

–埃因·坎贝尔
09年5月12日在8:30

#6 楼

这是因为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);


#7 楼

触发器是解决此问题的方法:

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


#8 楼

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

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


或完全关闭此功能:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();


#9 楼

对于使用ASP.NET Core 2.0和EF Core 2.0遇到的此问题,我的解决方案是按以下顺序执行以下操作:在程序包管理控制台(PMC)中运行update-database命令以创建数据库(这会导致“引入FOREIGN KEY约束...可能会导致循环或多个级联路径。”错误)
在PMC中运行script-migration -Idempotent命令以创建一个脚本,无论现有的表/约束如何,该脚本都可以运行
采用生成的脚本并找到ON DELETE CASCADE并替换为ON DELETE NO ACTION
现在对数据库执行修改后的SQL

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

太糟糕了,我无法在Entity Framework Core 2.0中找到任何方法来实现此目的。

祝你好运!

评论


您可以更改迁移文件来执行此操作(无需更改sql脚本),即,在迁移文件中,您可以将onDelete操作设置为“从级联限制”

–鲁西·索尼(Rushi Soni)
18 Mar 17 '18 at 11:46

最好使用流利的注释来指定它,这样,如果最终删除并重新创建迁移文件夹,就不必记住要这样做。

–王艾伦
18年5月1日在15:08

以我的经验,可以使用并且应该使用流畅的注释(我使用它们),但是它们通常是相当麻烦的。仅在代码中指定它们并不总是能产生预期的结果。

–user1477388
18年5月2日在10:21