首页 文章

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

提问于
浏览
152

当我尝试向表格添加约束时遇到问题 . 我收到错误:

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

我的约束是在 Code 表和 employee 表之间 . Code 表包含 IdNameFriendlyNameTypeValue . employee 有许多引用代码的字段,因此可以为每种类型的代码提供引用 .

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

我有什么想法可以做到这一点?

9 回答

  • -2

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

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

  • 4

    具有多个级联路径的典型情况是:具有两个细节的主表,例如“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记录永远不属于两个细节,现在也不可能为这两个细节制作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记录(实际上是先创建记录以获得正确且唯一的id) .

  • 159

    我会指出(功能上)在SCHEMA和DATA中循环和/或多个路径之间存在巨大差异 . 虽然DATA中的循环和可能的多路径肯定会使处理变得复杂并导致性能问题(“正确”处理的成本),但架构中这些特性的成本应该接近于零 .

    由于RDB中的大多数表观周期都出现在层次结构(组织结构图,部分,子部分等)中,因此不幸的是SQL Server假设最糟糕;即,模式周期==数据周期 . 实际上,如果您使用RI约束,则无法在数据中实际构建循环!

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

    当然,如果SQL Server确实允许循环,那么'd still be subject to a depth of 32, but that'对于大多数情况来说可能是足够的 . (太糟糕了,但这不是数据库设置!)

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

    Celko提出了一种“更好”的方式来表示不引入周期的层次结构,但存在权衡 .

  • 0

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

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

  • 11

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

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

    例如 . 更新员工,导致代码由On Update Action更改,导致Employees被On Update Action更改......等等...

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

  • 0

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

    这是类型数据库触发器策略的错误 . A trigger is code and can add some intelligences or conditions to a Cascade relation like Cascade Deletion. 您可能需要专门设置相关的表选项,例如关闭CascadeOnDelete:

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

    或者完全关闭此功能:

    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
    
  • 81

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

    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
    
  • 1

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

    • 在程序包管理控制台(PMC)中运行 update-database 命令以创建数据库(这会导致"Introducing FOREIGN KEY constraint ... may cause cycles or multiple cascade paths."错误)

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

    • 获取生成的脚本并找到 ON DELETE CASCADE 并替换为 ON DELETE NO ACTION

    • 对数据库执行修改后的SQL

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

    太糟糕了,我无法在Entity Framework Core 2.0中找到任何方法 .

    祝好运!

相关问题