Search code examples
c#ef-code-firstef-fluent-apief-core-2.2.net-core-2.2

Avoiding cyclic cascading with n-to-m relations and a parent in EF Core 2.2 with Fluent API


Following the guide for configuring 1-to-m in combination with n-to-m, I set up a system of School containing multiple Student and Course instances. Each Student instance may contain a single link to a certain Course instance (and vice versa). A Student instance can contain multiple such links (and vice versa).

This works when taken separately, for those guides. But when combining them, I'm getting the following error message when updating database, although the migration as such is generated correctly.

Introducing FOREIGN KEY constraint 'FK_Student_Course_Students_StudentId' on table 'Student_Course' 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.

After a while I realized that the issue occurs due to my school cascading on delete to both students and courses. Those, in turn, have a cascade to the n-to-m relation between them. And that's a booboo because once deleted, it can't be re-deleted.

The complication is that I might delete only a course (and then want to cascade from it) but I also might delete only a student (and want the cascade from that).

So I need to cover the following cases.

  1. School deleted -> each student deleted, each course deleted, each student-course-ralation deleted
  2. Student deleted -> student-course-ralation deleted
  3. Course deleted -> student-course-ralation deleted

How should I reconfigure my model builder to only cause one deletion branch?

The current configuration is set like this.

builder.Entity<Student_Course>().HasKey(sc => new { sc.StudentId, sc.CourseId });
builder.Entity<School>().HasMany(e => e.Courses)
  .WithOne(e => e.School).HasForeignKey(e => e.SchooldId);
builder.Entity<School>().HasMany(e => e.Students)
  .WithOne(e => e.School).HasForeignKey(e => e.SchooldId);

And the model looks like this.

public class School
{
  public Guid Id { get; set; }
  public virtual IList<Student> Students { get; set; }
  public virtual IList<Course> Courses { get; set; }
}

public class Student
{
  public Guid Id { get; set; }
  public virtual IList<Student_Course> Student_Course { get; set; }
  public Guid SchooldId { get; set; }
  public virtual School School { get; set; }
}

public class Course
{
  public Guid Id { get; set; }
  public virtual IList<Student_Course> StudentCourses { get; set; }
  public Guid SchooldId { get; set; }
  public virtual School School { get; set; }
}

public class Student_Course
{
  public Guid StudentId { get; set; }
  public virtual Student Student { get; set; }
  public Guid CourseId { get; set; }
  public virtual Course Course { get; set; }
}

Solution

  • Apparently, as insanely chocking it sounds, it can't be done.

    Source: "...infamous SQL Server limitation..." as @gertarnold provided.

    Suggested way to handle it is to manually introduce business logic to handle that explicitly.