ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我的约束介于 Code 表和 employee 表之间。 Code 表包含 IdNameFriendlyNameTypeValueemployee 有许多引用代码的字段,因此可以对每种类型的代码进行引用。

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

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

解决方案之一是 here

Á
Álvaro González

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

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

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


我仍然无法理解的一件事是,如果这个“问题”可以通过使用触发器来解决,那么触发器为什么不会“导致循环或多个级联路径......”?
@armen:因为您的触发器将显式提供系统无法自行隐式计算的逻辑,例如,如果删除引用操作有多个路径,那么您的触发器代码将定义删除哪些表以及删除顺序。
并且触发器在第一个操作完成后执行,因此没有竞争。
@dumbledad:我的意思是,只有在约束(可能是组合)无法完成工作时才使用触发器。约束是声明性的,它们的实现是系统的责任。触发器是过程代码,您必须对实现进行编码(和调试)并忍受它们的缺点(更差的性能等)。
这样做的问题是,触发器仅在您删除外键约束时才起作用,这意味着您对数据库插入没有参照完整性检查,因此您需要更多触发器来处理它。触发器解决方案是一个导致退化数据库设计的兔子洞。
C
Chuck Norris

具有多个级联路径的典型情况是这样的:具有两个详细信息的主表,例如“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)。


您的评论帮助我了解了我面临的问题。谢谢!我宁愿关闭其中一个路径的级联删除,然后以其他方式处理其他记录的删除(存储过程;触发器;通过代码等)。但是我会牢记您的解决方案(在一条路径中分组),以应对同一问题的可能不同应用......
一个用于使用“症结”一词(也用于解释)
这比编写触发器更好吗?添加一个额外的表只是为了让级联工作似乎很奇怪。
任何事情都比编写触发器更好。它们的逻辑是不透明的,与其他任何东西相比它们效率低下。将大表分解成更小的表以进行更精细的控制只是一个更好的规范化数据库的自然结果,而这本身并不值得关注。
B
Bill Cohagan

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

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

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

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

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

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


“如果您使用 RI 约束,您实际上无法在数据中建立循环!” - 好点子!
当然,您可以构建数据循环,但 MSSQL 只能使用 UPDATE。其他 RDBM 支持延迟约束(在提交时确保完整性,而不是在插入/更新/删除时)。
J
Javier

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

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


M
Mihai Alexandru-Ionut

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

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

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

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


它们相当长,所以我不认为我可以在这里发布它们,但我非常感谢您的帮助 - 不知道是否有某种方式可以将它们发送给您?我会尝试描述它:唯一存在的约束来自 3 个表,这些表都具有通过简单的 INT Id 键引用代码的字段。问题似乎是 Employee 有几个引用代码表的字段,我希望它们都级联到 SET NULL。我所需要的是,当代码被删除时,对它们的引用应在任何地方设置为 null。
无论如何都要发布它们......我认为这里的任何人都不会介意,代码窗口将在滚动块中正确格式化它们:)
R
Rajnikant

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

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

T
Tone Škoda

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

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

A
Amirhossein Mehrvarzi

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

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

或完全关闭此功能:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

a
aseman arabsorkhi

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


L
Ludovic Aubert

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

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

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

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

避免所有的麻烦。


u
user1477388

我使用 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 操作设置为 Restrict from Cascade
最好使用流利的注释来指定它,这样如果您最终删除并重新创建迁移文件夹,您就不必记住这样做。
以我的经验,可以使用并且应该使用流畅的注释(我使用它们),但它们通常有很多错误。简单地在代码中指定它们并不总是能产生预期的结果。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅