Entity Framework Code first: cycles or multiple cascade paths

James Peters picture James Peters · Mar 15, 2015 · Viewed 8.5k times · Source

I have a Booking class that has a booking contact (a Person) and a set of navigation properties (People) that links through a join table to another set of navigation properties (Bookings) in Person. How do I generate the Booking table with cascading deletes enabled for the booking contact relationship? When I leave it out of the fluent API code (default setting of cascade delete enabled) I get the following error message from migration:

Introducing FOREIGN KEY constraint 'FK_dbo.BookingPeople_dbo.People_PersonID' on table 'BookingPeople' 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.

 modelBuilder.Entity<Person>()
   .HasMany<Booking>(s => s.aBookings)
   .WithRequired(s => s.Contact)
   .HasForeignKey(s => s.ContactId); 


 modelBuilder.Entity<Booking>()
   .HasMany(t => t.People)
   .WithMany(t => t.Bookings)
   .Map(m => {
     m.ToTable("BookingPeople");
     m.MapLeftKey("BookingID");
     m.MapRightKey("PersonID");
   });

Answer

octavioccl picture octavioccl · Mar 15, 2015

The problem is you have multiple paths of cascade deletes that could end trying to delete the same row in the BookingPeople table in DB.

You can avoid such ambiguous delete paths by either disabling cascading delete in the one-to-many relationship using Fluent API:

    modelBuilder.Entity<Booking>()
                .HasRequired(s => s.Contact)
                .WithMany(s => s.aBookings)
                .HasForeignKey(s => s.ContactId)
                .WillCascadeOnDelete(false);

Or by defining the relationship as optional (with a nullable foreign key, but you can not configure the relationship with cascade delete using Fluent Api).

     modelBuilder.Entity<Booking>()
            .HasOptional(s => s.Contact)
            .WithMany(s => s.aBookings)
            .HasForeignKey(s => s.ContactId);// ContactId is a nullable FK property

Also, you can remove the cascade delete convention by using:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

Or in the case of the many-to-many relationship:

modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

If you need to delete all the Bookings asociated with a Person when you delete it, my advice is configure the one-to-many relationship as optional, and override the SaveChanges method:

public override int SaveChanges()
{
    Bookings.Local
            .Where(r => r.ContactId == null)
            .ToList()
            .ForEach(r => Bookings.Remove(r));

    return base.SaveChanges();
 }

If a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship, and when the principal is deleted the foreign key will be set to null. This way, you can find the orphans in the SaveChanges method and delete them