如何在有循环和多个级联路径的情况下配置级联删除

Posted

技术标签:

【中文标题】如何在有循环和多个级联路径的情况下配置级联删除【英文标题】:How to configure cascade delete where there are cycles and multiple cascade paths 【发布时间】:2019-07-12 02:47:05 【问题描述】:

我很难理解这种关系,以及如何为其设置级联删除设置。

有一个员工表,每个员工都有任意数量的句柄、附件和工作 有一个句柄表,其中每个句柄都属于一个员工,并且可以在一个工具中使用 有一个附件表,其中每个附件都属于一个员工,并且可以在一个工具中使用 有一个工具表,其中每个工具由一个附件和一个手柄组成,可用于任意数量的工作 有一个工作表,其中每个工作属于一个员工,并且可能有也可能没有使用的工具

注意:手柄和附件可能存在而不用于制作工具

简而言之:员工可以混合搭配手柄和附件来制作工具,然后在分配给他们的工作中使用工具。

此图显示了数据库是如何连接在一起的(欢迎提出更好的设计建议

DB Diagram

这就是模型的设置方式,作业模型具有对工具 FK (ToolId) 的可为空引用,因此作业可以在没有工具的情况下存在。

public class Employee

    public int EmployeeId  get; set; 
    public string Name  get; set; 

    public List<Handle> Handles  get; set; 
    public List<Attachment> Attachments  get; set; 
    public List<Job> Jobs  get; set; 

public class Handle

    public int HandleId  get; set; 
    public string Material  get; set; 
    public double ExpectedLife  get; set; 
    public double LifetimeMaintenance  get; set; 

    public int EmployeeId  get; set; 
    public Employee Employee  get; set; 
    public List<Tool> Tools  get; set; 

public class Attachment

    public int AttachmentId  get; set; 
    public string Material  get; set; 
    public string Type  get; set; 
    public double ExpectedLife  get; set; 
    public double LifetimeMaintenance  get; set; 

    public int EmployeeId  get; set; 
    public Employee Employee  get; set; 
    public List<Tool> Tools  get; set; 

public class Tool

    public int ToolId  get; set; 
    public string OperationSpeed  get; set; 


    public int HandleId  get; set; 
    public Handle Handle  get; set; 

    public int AttachmentId  get; set; 
    public Attachment Attachment  get; set; 

    public List<Job> Jobs  get; set; 

public class Job

    public int JobId  get; set; 
    public string Name  get; set; 
    public double EffortRequired  get; set; 

    public int EmployeeID  get; set; 
    public Employee Employee  get; set; 
    public int? ToolId  get; set; 
    public Tool Tool  get; set; 

这就是创建数据库上下文的方式。有一个级联删除设置,用于在删除工具时将 Jobs (ToolId) 中的工具 FK 设置为 null(因此删除工具时不会删除作业)。

public class ToolsDbContext : DbContext

    public ToolsDbContext(DbContextOptions<ToolsDbContext> options) : base(options)
    

    

    public DbSet<Employee> employees  get; set; 
    public DbSet<Handle> handles  get; set; 
    public DbSet<Attachment> attachments  get; set; 
    public DbSet<Tool> tools  get; set; 
    public DbSet<Job> jobs  get; set; 

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    
        modelBuilder.Entity<Tool>()
            .HasMany(j => j.Jobs)
            .WithOne(t => t.Tool)
            .OnDelete(DeleteBehavior.SetNull);
    

创建迁移工作,但更新数据库失败并出现以下错误:

Introducing FOREIGN KEY constraint 'FK_tools_handles_HandleId' on table 'tools' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.

我不太清楚如何理解这个错误。

深思熟虑:

如果一个句柄被删除,它将删除所有使用它的工具,这反过来又会将相关作业中的 ToolId 设置为 null 如果一个附件被删除,它会删除它所使用的所有工具,进而将相关作业中的 ToolId 设置为 null 如果一个工具被删除,它会将相关作业中的 ToolId 设置为 null 如果作业被删除,将不会产生级联效应

因此,我认为问题一定出在删除员工上,但我不明白为什么(还没有?)...

如果删除了员工,则应删除所有内容;它应该删除所有相关的作业、句柄和附件。然后那些被删除的句柄或附件应该反过来删除与它们相关的工具(不管什么先出现)。

所以有删除员工的级联路径,但我希望这一切都可以根据模型设置按原样工作......所以我需要在 dbcontext 中配置更多级联删除要求吗?如果是这样,我不确定它应该如何配置......

注意:如果没有数据库中的雇员模型,一切似乎都正常

【问题讨论】:

【参考方案1】:

SQL 服务器不允许有多个级联路径到数据库中的同一个表。在您的情况下,其中有两个用于工具:

员工 -> 句柄 -> 工具 员工 -> 附件 -> 工具

解决问题的所有方法都包括为一种关系或另一种关系设置 DeleteBehavior.Restrict,例如:

为实体设置 DeleteBehavior.Restrict -> 处理关系并通过触发器处理此级联路径(否则“限制”将不允许删除引用它的记录) 为实体设置 DeleteBehavior.Restrict -> 在应用程序代码中处理关系并处理此级联路径(在删除主要实体之前明确更新/删除所有相关实体) 为两种实体关系设置“限制”行为

等等……

【讨论】:

我明白了;因此,仅设置modelBuilder.Entity&lt;Handle&gt;().HasMany(t =&gt; t.Tools).WithOne(h =&gt; h.Handle).OnDelete(DeleteBehavior.Restrict); 现在将允许删除“员工”条目,并且所有相关条目都将通过指向它们的路径删除。但是当句柄被删除时,任何依赖条目都必须以其他方式处理。尽管我在将那一行添加到上下文并进行测试后注意到,但如果我尝试删除“工具”或“附件”,则会导致错误。我错过了什么吗?这一切似乎比它应该的要复杂一些......也许我错过了一些东西。 它应该不会影响删除工具或附件,您可以发送您遇到的错误吗?另请注意,删除员工将触发句柄删除,因此“任何相关条目都必须以其他方式处理”也适用于此处。在应用程序代码中处理这种复杂删除的方法之一是在删除之前急切地加载相关记录 - 它会生成所有必要的 DELETE 语句。【参考方案2】:

你说:

有一张员工表,每个员工都有任意数量的句柄、附件和工作

但是您的图表在员工和句柄之间建立了直接联系,一个句柄有许多员工,而一个员工只有一个句柄

您的陈述与您的图表相冲突

从数据库的角度来看,我认为这种建模是错误的。我认为工作应该有员工。 (如果一个工作有多个员工,您将需要另一个表 jobemployees 将一个工作 ID 映射到多个员工。)一个工作有一个工具,一个工具有一个句柄和一个附件。我不明白为什么删除员工应该删除他们的工作(如果我解雇了某人,他为我工作时建造的房子仍然存在)但你可以在不使用级联约束的情况下清理它

最终,您可以在图表中看到您创建的循环。如果删除 1 端的某些内容会删除 * 端的所有内容,那么删除图表中的任何内容都会启动一个链,该链采用一条拆分路径,该路径重新组合在一起。删除员工实体确实打破了这一点

最终,员工不应该直接拥有工作、把手或附件

【讨论】:

以上是关于如何在有循环和多个级联路径的情况下配置级联删除的主要内容,如果未能解决你的问题,请参考以下文章

引入 FOREIGN KEY 约束可能会导致循环或多个级联路径 - 为啥?

OnDelete(DeleteBehavior.Cascade) 可能会导致循环或多个级联路径

外键约束可能导致循环或多个级联路径[重复]

MSSQL约束可能导致循环或多个级联路径[重复]

FOREIGN KEY可能会导致循环或多个级联路径异常[重复]

更新迁移到数据库时出错:外键约束可能导致循环或多个级联路径