Entity Framework Code first: cycles or multiple cascade paths

c# ef-fluent-api entity-framework sql-server

Question

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");
   });
1
4
4/30/2015 11:15:55 PM

Accepted Answer

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

20
3/15/2015 11:24:22 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow