Entity Framework Core Cascade Delete is one of the Referential actions. These actions specify what to do with the related rows when we delete the parent row. We can configure these actions in the database and in the EF core (Client). The options available to use are Delete the related rows (Cascade / ClientCascade), Update its Foreign Key to NULL (SetNull / ClientSetNull) or do nothing (Restrict/ NoAction / ClientNoAction). We setup delete behavior between entities using the FluentAPI OnDelete method.
The ClientCascade, NoAction & ClientNoAction are added in EF Core 3.0. If you were using Restrict then code will break. You should change it to ClientNoAction
Table of Contents
Relationships Recap
The entities participating in the relationship can be either Principal entity (parent entity) or Dependent Entity (child entity). The principal entity is the one that can exist on its own. The Dependent Entity depends on Principal Entity.
For Example, consider the relationship between Employee
and Department
entity. The Department
entity is the Principal entity as it does not need an Employee
entity. But the Employees must belong to a department. Hence Employee
entity is a Dependent Entity.
The Foreign Key is the glue between Principal Entity and its Dependent Entity. It can be either Required or Optional. If the Foreign Key is optional, then we can store the NULL value in it.
The database should not allow us to delete the parent record when it has a child record. it must keep the integrity of the data. It does so by using Referential actions. They are the actions that the database will take when we delete a parent record.
For Example, in an SQL Server database defines the four Referential actions. They are ON DELETE CASCADE
, ON DELETE SET NULL
, ON DELETE NO ACTION
& ON DELETE SET DEFAULT
When we delete a parent record, the database will take one of the following actions based on the Referential actions.
- ON DELETE CASCADE: Delete the Child Records. (i.e. Cascade)
- ON DELETE SET NULL: Assign the NULL to the Foreign Key in the Child table (i.e SetNull).
- ON DELETE NO ACTION: Do nothing. The database will throw an error in case of Foreign Key violations. (Restrict/ NoAction)
- ON DELETE SET DEFAULT: Assign the Default Value. The database will throw an error if the default value breaks the Foreign Key rules.
The Entity Framework Core also defines the Referential actions. It refer them as Delete behaviors.
You can read more about relationships in entity framework core
Delete behaviors
The Entity Framework Core defines two sets of delete behaviors. Behaviors that maps to the database & those who do not. Those who do not map to the database starts with the prefix Client
.
- Cascade: Delete the Child Records both in client & Database.
- ClientCascade: Delete the Child Records both in the client only.
- SetNull: Set Foreign Key as NULL in both in Client & Database.
- ClientSetNull: Set Foreign Key as NULL only in the Client
- NoAction: Default behavior on the client and No action on the database
- ClientNoAction: No action on the client and on the database
- Restrict: Same as NoAction. The migrations script will generate (Restrict or Non) instead of NoAction.
Setting the Delete behaviors
The EF Core defines the Delete behaviors in the DeleteBehavior enumerator. We can specify them while defining the relationships using the Fluent API.
For Example. The employee & the Department has one to many relationships. We setup delete behavior between them using the OnDelete
method as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class Employee { [Key] public int EmployeeID { get; set; } public string Name { get; set; } [ForeignKey("Department")] public int DepartmentID { get; set; } public virtual Department Department { get; set; } } public class Department { public int DepartmentID { get; set; } public string Name { get; set; } public virtual ICollection<Employee> Employees { get; set; } } |
The .OnDelete(DeleteBehavior.Cascade)
assigns the delete Behavior to this relationship
1 2 3 4 5 6 7 8 | modelBuilder.Entity<Employee>() .HasOne<Department>(e => e.Department) .WithMany(d => d.Employees) .HasForeignKey(e => e.DepartmentID) .IsRequired(true) .OnDelete(DeleteBehavior.Cascade) ; //Setup delete behaviour here |
You can refer to how to setup relationships in EF Core from the following articles
Behaviors that maps to the database
The Cascade
, SetNull
, NoAction
& Restrict
are the actions that map to database referential action.
When you use the above behaviors to configure the relationship and use EF Core Migration to create the database, then the following cascade behavior will be set up for you.
Action | Mapped to database |
---|---|
Cascade | ON DELETE CASCADE |
NoAction | ON DELETE NO ACTION |
SetNull | ON DELETE SET NULL |
Restrict | ON DELETE RESTRICT (Not supported by SQL SERVER) |
For Example, the following Fluent API DeleteBehavior.Cascade
mapping
1 2 3 4 5 6 7 8 | modelBuilder.Entity<Employee>() .HasOne<Department>(e => e.Department) .WithMany(d => d.Employees) .HasForeignKey(e => e.DepartmentID) .IsRequired(true) .OnDelete(DeleteBehavior.Cascade) ; //Setup delete behaviour here |
Results in the Employee table with CASCADE DELETE
1 2 3 4 5 6 7 8 9 | CREATE TABLE [Employees] ( [EmployeeID] int NOT NULL IDENTITY, [Name] nvarchar(max) NULL, [DepartmentID] int NOT NULL, CONSTRAINT [PK_Employees] PRIMARY KEY ([EmployeeID]), CONSTRAINT [FK_Employees_Departments_DepartmentID] FOREIGN KEY ([DepartmentID]) REFERENCES [Departments] ([DepartmentID]) ON DELETE CASCADE ); |
If you are not using migrations then you will have to configure them manually in the database.
Behaviors that do not map to the database
If you are using ClientCascade
, ClientSetNull
, or ClientNoAction
, then Migration will not generate the referential actions in the database.
That is the only difference between them.
Delete Behaviours in Tracked Entities
The Delete behavior only works on the entities that we load in the Memory (Tracked Entities). It will have no effect on the entities that we do not load.
For Database to delete the related entities we need to set up the Referential actions like FOR DELETE CASCADE
or FOR DELETE SET NULL
when we set up the ForeignKey.
For Example, consider the following query where we are deleting the department with id 3. It will have no effect on Employee Records as they are not loaded. When we call SaveChanges, DBContext will send the query to delete the department.
1 2 3 4 5 6 7 | var dept= db.Departments .Where(f => f.DepartmentID == 3) .FirstOrDefault(); db.Departments.Remove(dept); db.SaveChanges(); |
In the following query, we eagerly load the employee entity along with the Department using the Include method. The EF is now tracking both Department & Employee records.
1 2 3 4 5 6 7 8 | var dept= db.Departments .Where(f => f.DepartmentID == 3) .Include(f=>f.Employees) .FirstOrDefault(); db.Departments.Remove(dept); db.SaveChanges(); |
When we delete the Department (db.Departments.Remove(dept)
), depending on how we set up the DeleteBehavior
, DbContext will do one of the following
DeleteBehavior.Cascade
/DeleteBehavior.ClientCascade
Delete the Employee EntitiesDeleteBehavior.ClientSetNull
/DeleteBehavior.SetNull
:
Updates the DepartmentID of Employee record to NULLDeleteBehavior.NoAction
Updates the DepartmentID of Employee record to NULLDeleteBehavior.ClientNoAction
:
Does nothing.DeleteBehavior.Restrict
:
Updates the DepartmentID of Employee record to NULL
All of the above actions happen on the Client Side. When we call SaveChanges, DBContext will send the query to delete the department and also delete/update the tracked employee records
How untracked entity is treated depends in the delete behavior of the Database
Delete behaviors in Database
For Untracked entities, The database will do one of the following
ON DELETE CASCADE
: The Database will delete the Employee records.ON DELETE SET NULL
: TheDepartmentID
the field is set to NULL in Employee Records.ON DELETE NO ACTION
: The Database does nothing.ON DELETE SET DEFAULT
: The DepartmentID is set to the default value.- No Referential action: The Database does nothing. This is same as
ON DELETE NO ACTION
Whether Tracked or untracked if any of the results of the operation violates the FOREIGN KEY constraint, then the database will raise an error.
Delete behaviors in Detail
Cascade / ClientCascade
The Context deletes the entities it tracks, when we delete the parent.
Cascade
option creates a migration script with ON DELETE CASCADE
, while ClientCascade
creates a migration script with ON DELETE NO ACTION
.
Cascade
is the default behavior, when using the required relationship i.e Foreign key is Not Nullable
Untracked entities if any are deleted if the ON DELETE CASCADE
is setup in the database. If not results in a FOREIGN KEY violation.
Not all databases support Cascade
or does not support fully. Especially if there are cycles in relationships. Use ClientCascade
and load all related entities before deleting the parent. The context will perform the Cascade Delete
on the client-side.
SetNull / ClientSetNul
When we delete the parent, the Context sets values of foreign key properties in dependent entities to null on the entities it is tracking.
ClientSetNull
is the default behavior, when using the optional relationship i.e Foreign key is nullable
SetNull
option creates a migration script with ON DELETE SET NULL
, while ClientSetNull
creates a migration script with ON DELETE NO ACTION
.
Untracked entities if any are also set to NULL if ON DELETE SET NULL
is set up in the database. If not results in a FOREIGN KEY violation.
NoAction
When we delete the parent, the Context sets values of foreign key properties in dependent entities to null on the entities it is tracking.
Does not generate any migration script.
Database will throw an error in case of any FOREIGN KEY violation.
ClientNoAction
The Context does not modify or delete the entities it is tracking when we delete the parent
Does not generate any migration script.
Database will throw an error in case of any FOREIGN KEY violation.
Restrict
When we delete the parent, the Context sets values of foreign key properties of the dependent entities to null on the entities it is tracking.
Restrict
option creates a migration script with ON DELETE NO ACTION
Database will throw an error in case of any FOREIGN KEY violation.