Cascading entity deletes
Cascading deletes, if not configured properly, can cause cycles.
Problem
Cascade delete means that if an entity is deleted, all of its dependent entities will be removed too. This makes sense in those cases where a child cannot exist without its parent; imagine a blog and its posts, for example. This is how Entity Framework deletes these dependent entities:
If we are using a database engine that supports cascades in constraints (such as SQL Server) and Entity Framework was used to create the database, it will use them
If the dependent entities are not loaded, they will be deleted with a single
DELETE
statementIf we cannot delete them, we can set the relation property to
NULL
; unfortunately, this has to be done one by one for all the related entities
Problems start to arise if we let Entity Framework set up the cascade constraints in the database and there are cycles, meaning the deletion of a record cascades to other records, possibly in different tables, which in turn will trigger a deletion on a third level table, like in the following diagram:
We cannot let SQL Server manage these cascade deletions through foreign key constraints because it would result in an error when the database is created.
How to solve it…
Cascade deletions are enabled by default for required relations but can be configured explicitly. For that, we use the fluent API:
protected override void OnModelCreating( ModelBuilder modelBuilder) { modelBuilder .Entity<Parent>() .HasMany(c => c.Children2) .WithOne(c => c.Parent) .OnDelete(DeleteBehavior.SetNull); base.OnModelCreating(modelBuilder); }
And we also use this class model:
public class Parent { public int Id { get; set; } public string Name { get; set; } [InverseProperty("Parent")] public ICollection<Child1> Children1 { get; set; } [InverseProperty("Parent")] public ICollection<Child2> Children2 { get; set; } } public class Child1 { public int Id { get; set; } public int Option { get; set; } [Required] public Parent Parent { get; set; } [InverseProperty("Child1")] public ICollection<GrandChild> GrandChildren { get; set; } } public class Child2 { public int Id { get; set; } public string Text { get; set; } public Parent Parent { get; set; } [InverseProperty("Child2")] public ICollection<GrandChild> GrandChildren { get; set; } } public class GrandChild { public int Id { get; set; } public byte [] Payload { get; set; } [Required] public Child1 Child1 { get; set; } [Required] public Child2 Child2 { get; set; } }
Note
Do not pay attention to the names of the classes and their properties; this is just a simple example.
Notice that for the Child2
class, we do not mark the Parent
property as required, this is what allows us to use the SetNull
cascade option. Also, we do not need to specify the cascade delete behavior for all the other relations, this is the default.
So, with this setup, what happens if we delete a Parent
instance is this:
If the
Children1
orChildren2
collections have not been loaded, Entity Framework will only remove records from theParent
tableAll loaded entities in the
Child1
table that are related with theParent
instance (Children1
collection) are deleted with a singleDELETE
commandAll loaded entities in the
Child2
table that are related withParent
(Children2
) have theirParent
property set toNULL
one by oneIf any of the
GrandChildren
collections was loaded, the entries in theGrandChild
table stored in it will be deleted one by one
Of course, if we want, we can disable cascade deletes altogether:
modelBuilder .Entity<Child1>() .HasMany(c => c.GrandChildren) .WithOne(c => c.Child1) .OnDelete(DeleteBehavior.Restrict);
However, in this case we must not forget to remove the dependent entities before removing the principal one.