Missing EF Feature Workarounds: Cascade delete orphans

Currently, Entity Framework cannot cascade delete orphans. In fact, there’s not a real concept of parent-child relationships, there’s only navigation properties, collection properties, and a notion of required/optional relationships. You can cascade delete if a parent is deleted, but if you try something like this:

var customer = dbContext.Customers.Find(10);

customer.Addresses.RemoveAt(0);

dbContext.SaveChanges();

// We now have an orphaned Address!

You’ll have an orphaned address! In a parent-child relationship, you’d like the parent to completely own the relationship to the child. Children cannot live on their own, so even if you configure EF to have a required relationship from child to parent:

public class CustomerContext : DbContext {
    protected override OnModelCreating(DbModelBuilder modelBuilder) {
        modelBuilder.Entity<Address>()
            .HasRequired(a => a.Customer);
    }
}

You’ll get an error on SaveChanges about a required relationship not being available. What we’d like to do is when true child entities get detached from their parent, they get deleted. There’s no way to do this in the entity mapping, but we can override SaveChanges in our custom DbContext class to detect orphans and then delete them:

public class CustomerContext : DbContext {
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Address> Addresses { get; set; }
    public override int SaveChanges() {
        foreach (var orphan in Addresses.Local.Where(a => a.Customer == null)) {
            Addresses.Remove(orphan);
        }
    }
}

Not terrible, but it preserves the encapsulation of my parent-child relationship from Customer and Address.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in Entity Framework. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Harry McIntyre

    Sometime I wonder if parent-child for aggregate modelling would be a good idea to have implemented as a language feature.

    • jbogard

      Tough because we can’t ignore the DB modeling, though.

  • Bob Archer

    Isn’t this only going to work if you included the Children in the original query or you have lazy loading enabled? This should be controlled by cascade deletes in the database. Just because you’re using an ORM, don’t ignore the database server features.

    • jbogard

      This is a different kind of cascade delete – I want to delete *orphans*, when they’re detached from a parent. The parent is still around.

  • Brock Allen
    • jbogard

      Ah nice, thanks!

  • jwdenny13

    I really like this solution but is it possible to add this behavior dynamically? In my current project we have a large database so it would be nice if SaveChanges() could dynamically figure out which tables fit this criteria and automatically remove the orphaned records.

  • Pingback: Missing EF Feature Workarounds: Cascade delete orphans | Dot Net RSS

  • Trevor E

    If I may tag on to this discussion, this resolved my error:

    The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted

    I thought it might be helpful if this text was tagged to this article so guys like me could find it while googling for the error. The guys at Arana pointed me to this article or I would have never found it.

  • Fzznik

    I found very recently an even simpler solution with EF6. No need to override SaveChanges, specify CascadeOnDelete, or bind any event handlers to your DbContext. The simple trick is to make the relationship between Customers and Addresses an “identifying relationship,” and EF will know when you remove the address from the customer (making it an orphan), that the record should be deleted.

    What this means (in your example) is that the primary key on the Addresses table would be composite, including both the AddressID and the CustomerID. (You can still have AddressID be an auto-incrementing Identity field.)

    // A composite key on the Address like this indicates we have an “identifying relationship”
    modelBuilder.Entity().HasKey(a => new { a.AddressID, a.CustomerID });
    // And for keeping the auto-increment field, if using one:
    modelBuilder.Entity().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

    More info : http://msdn.microsoft.com/en-us/library/ee373856.aspx

    • Fzznik

      Whoops, the last line I wrote for keeping the auto-incrementing field forgot to include .Property(a => a.AddressID) in front of the HasDatabaseGeneratedOption…

      modelBuilder.Entity
      .Property(a => a.AddressID)
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);