简体   繁体   中英

How to configure cascade delete where there are cycles and multiple cascade paths

I'm having a bit of trouble getting my head around this relationship, and how to possibly setup cascade delete settings for it.

  • There is a table of employees, where each employee has any number of handles, attachments and jobs
  • There is a table of handles, where each handle belongs to an employee and may be used in a tool
  • There is a table of attachments, where each attachment belongs to an employee and may be used in a tool
  • There is a table of tools, where each tool is made up of one attachment, one handle and is used on any number of jobs
  • There is a table of jobs, where each job belongs to an employee, and may or may not have a tool used on it

Note: it's possible for handles and attachments to exist without being used to make a tool

In short: an employee can mix-and-match handles and attachments to make tools, and then use a tool on a job they are assigned.

This diagram shows how the database is wired together ( feel free to suggest a better design )

DB Diagram

This is how the models are setup, the Job model has a nullable reference to the tools FK (ToolId) so a job can exist without a tool.

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

This is how the DB context has been created. There is a cascade delete setting to set the tool FK in Jobs (ToolId) to null when a tool is deleted (so the job wont get deleted when its tool is deleted).

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

Creating the migration works, but then updating the database fails with the following error:

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.

I'm not too sure how to understand this error.

Thinking it through:

  • If a handle is deleted, it will delete all tools it's used in, which in turn will set ToolId in related Jobs to null
  • If an attachment is deleted, it will delete all tools it's used in, which in turn will set ToolId in related Jobs to null
  • If a tool is deleted it will set ToolId in related Jobs to null
  • If a job is deleted, there will be no cascade effect

Therefore I think the problem must be with deleting an employee, but I can't see why (yet?)...

  • If an employee is deleted everything should be deleted; it should delete all related jobs, handles and attachments. Then those deleted handles or attachments should in turn delete the tools associated with them (it shouldn't matter what came first).

So there is cascade paths deleting an employee, but I would expect this would all work based on the model setup as-is... So do I need to configure more cascade delete requirements in the dbcontext? If so, I'm not sure how it should be configured...

Note: without the employees model in the database, everything seems to work

SQL server doesn't allow to have multiple cascade paths to the same table in the database. In your case there are two of them for Tools:

  • Employee -> Handle -> Tool
  • Employee -> Attachment -> Tool

All ways to fix the issue consist in setting DeleteBehavior.Restrict for one relationship or the other, for example:

  • Setting DeleteBehavior.Restrict for Entity -> Handle relationship and handling this cascade path by a trigger (otherwise "restrict" won't allow to delete a record having references to it)
  • Setting DeleteBehavior.Restrict for Entity -> Handle relationship and handling this cascade path in application code (update/delete all related entities explicitly before deleting the main one)
  • Setting "restrict" behavior for both Entity relationships

etc...

You said:

There is a table of employees, where each employee has any number of handles, attachments and jobs

But your diagram establishes a direct link between an employee and a handle, one handle has many employees, and an employee has only one handle

Your statement is in conflict with your diagram

I think this modelling is wrong, from the database perspective. I think a job should have an employee. (If a job has multiple employees you'll need another table jobemployees that maps one job id to multiple employees.) A job has a tool, a tool has a handle and an attachment. I fail to see why deleting an employee should delete their jobs (if I fired someone the house he built while working for me still exists) but you can clean this up without using cascading constraints

Ultimately you can see in the diagram the cycle you've created. If deleting something at a 1 end deletes everything at the * end then deleting anything in your diagram starts a chain that takes a split path that comes back together. Removing the employee entity does indeed break this up

Ultimately an employee should not directly have a job, a handle or an attachment

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM