Cascade delete controls what happens to dependent entities when a related principal entity is deleted.
It is useful when deleting one entity should also control what happens to related data when SaveChangesAsync() runs.
Delete a Category with Related Products
Consider a Category that has many related Product entities.
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public List<Product> Products { get; set; } = new();
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; } = null!;
}
In this model, Category is the principal entity, and Product is the dependent entity because it contains the foreign key CategoryId.
Now delete a category and include its related products.
using var context = new AppDbContext();
var category = await context.Categories
.Include(c => c.Products)
.SingleAsync(c => c.Name == "Peripherals");
context.Categories.Remove(category);
await context.SaveChangesAsync();
If cascade delete is configured for this relationship, deleting the category can also delete the related products when the delete operation is saved.
The important point is that Remove() does not delete the rows immediately. EF Core tracks the category as Deleted, and SaveChangesAsync() sends the pending delete operation to the database.
For the basic Remove() + SaveChangesAsync() workflow, see Deleting Data.
What Is Cascade Delete?
Cascade delete means that deleting a principal entity can also delete the dependent entities that reference it.
For example, if a Category is deleted, its related Product rows may also be deleted:
Category
├── Product: Mechanical Keyboard
├── Product: Wireless Mouse
└── Product: 27-inch Monitor
After deleting the category, cascade delete can remove the related products too:
Category deleted
Products deleted
This helps avoid invalid foreign key values.
Without cascade delete, the products would still have a CategoryId value that points to a category that no longer exists. Most relational databases reject that state because it violates the foreign key constraint.
Configure DeleteBehavior.Cascade
Cascade delete is configured on a relationship.
You can configure it with the Fluent API by using OnDelete(DeleteBehavior.Cascade).
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.Cascade);
}
This configuration tells EF Core to use cascade delete for the Category / Product relationship.
DeleteBehavior.Cascade can delete the related products when the category delete is saved.
If the related products are loaded and tracked by the current DbContext, EF Core can apply the cascade behavior before sending the delete commands.
If the related products are not loaded, the database can apply the cascade behavior if the foreign key constraint was created with cascade delete enabled.
The exact SQL depends on the database provider, the relationship configuration, and whether the dependent entities are tracked.
Cascade Delete with Required Relationships
In the previous model, Product.CategoryId is not nullable.
public int CategoryId { get; set; }
That makes the relationship required.
A product cannot exist without a category because every product must have a valid CategoryId.
For required relationships, cascade delete is often the expected behavior because the dependent entity cannot remain valid without the principal entity.
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.Cascade);
In this configuration:
Categoryis the principal entityProductis the dependent entityProduct.CategoryIdis the foreign key- deleting a category can delete the related products
SaveChangesAsync()sends the pending delete operation to the database
Use Restrict or NoAction to Prevent Cascade Delete
Cascade delete is not always the safest choice.
If products should not be deleted automatically when a category is deleted, configure the relationship to prevent cascade delete.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.Restrict);
}
Restrict is used here to show the intention clearly: the category should not be deleted while products still depend on it.
You can also use DeleteBehavior.NoAction when you want the relationship to avoid automatic cascade delete and let the database constraint enforce referential integrity.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.NoAction);
}
Both Restrict and NoAction are restrictive behaviors. The exact database behavior can vary depending on the provider.
With this configuration, deleting a category that still has related products can fail because the products still reference the category.
using var context = new AppDbContext();
var category = await context.Categories
.Include(c => c.Products)
.SingleAsync(c => c.Name == "Peripherals");
context.Categories.Remove(category);
await context.SaveChangesAsync();
In a relational database, the delete can be rejected if it would leave related products with foreign key values that no longer point to an existing category.
This is useful when related data should be reviewed, moved, or deleted explicitly before deleting the principal entity.
Use SetNull for Optional Relationships
Cascade delete is not the only possible behavior.
If the dependent entity can exist without the principal entity, the foreign key can be nullable.
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public decimal Price { get; set; }
public int? CategoryId { get; set; }
public Category? Category { get; set; }
}
In this model, CategoryId is nullable.
That means a product can exist without a category.
You can configure the relationship to set the foreign key to null when the category is deleted.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.SetNull);
}
DeleteBehavior.SetNull can keep the related products in the database and set their CategoryId value to null when the category delete is saved.
Before delete:
Category: Peripherals
├── Product: Mechanical Keyboard CategoryId = 1
└── Product: Wireless Mouse CategoryId = 1
After delete:
Category deleted
Product: Mechanical Keyboard CategoryId = null
Product: Wireless Mouse CategoryId = null
Use this only when the relationship is optional.
If the foreign key is not nullable, the database cannot set it to null.
How Cascade Delete Works
Cascade delete is part of the normal EF Core saving workflow.
A simplified flow looks like this:
- You load or attach a principal entity.
- You call
Remove()on the principal entity. - EF Core tracks the principal entity as
Deleted. - You call
SaveChangesAsync(). - The configured delete behavior can be applied by EF Core for tracked dependents, or by the database through the foreign key constraint.
- The database receives the required
DELETEorUPDATEcommands. - The transaction succeeds or fails depending on the relationship configuration and database constraints.
This is the same Remove() + SaveChangesAsync() workflow used for normal deletes, but the configured relationship behavior decides what happens to the dependents.
With DeleteBehavior.Cascade, the dependent products can be marked for deletion.
With DeleteBehavior.SetNull, the dependent products can have their CategoryId values set to null.
With a restrictive behavior such as Restrict or NoAction, the operation can fail if products still reference the category.
EF Core Cascade Delete vs Database Cascade Delete
Cascade delete can be applied in two places:
- by EF Core, for entities tracked by the current
DbContext - by the database, through the foreign key constraint
When the related products are loaded and tracked, EF Core can apply the configured cascade behavior before saving.
using var context = new AppDbContext();
var category = await context.Categories
.Include(c => c.Products)
.SingleAsync(c => c.Name == "Peripherals");
context.Categories.Remove(category);
await context.SaveChangesAsync();
In this case, EF Core knows about the related products because they were loaded with Include().
When the related products are not loaded, the database constraint becomes more important.
using var context = new AppDbContext();
var category = await context.Categories
.SingleAsync(c => c.Name == "Peripherals");
context.Categories.Remove(category);
await context.SaveChangesAsync();
In this example, only the category is tracked by the current DbContext.
If the database foreign key constraint has cascade delete enabled, the database can delete the related products.
A cascade relationship can create a foreign key constraint similar to this:
CONSTRAINT [FK_Products_Categories_CategoryId]
FOREIGN KEY ([CategoryId]) REFERENCES [Categories] ([Id])
ON DELETE CASCADE
If the database constraint does not cascade and products still reference the category, the delete can fail with a foreign key constraint error.
The exact SQL depends on the database provider, the relationship configuration, and whether the dependent entities are tracked.
Important Behavior
Cascade delete depends on the relationship configuration.
That means:
Remove()does not delete rows immediatelySaveChangesAsync()is the point where the pending delete operation is sent and the configured behavior can take effect- cascade delete can apply from the principal entity to dependent entities
- required relationships often use cascade delete because dependents cannot exist without the principal
SetNullrequires a nullable foreign key- restrictive behavior can prevent accidental deletes
- the nullable or non-nullable foreign key influences which delete behaviors are valid
- the database can reject a delete if foreign key constraints would be violated
- EF Core can apply cascade behavior to tracked dependent entities
- the database can apply cascade behavior when the constraint is configured for it
For the basic entity delete workflow, see Deleting Data.
For more about how EF Core persists tracked changes, see SaveChanges.
For more about relationship configuration, see One-to-Many Relationship Configuration.
For the official EF Core documentation about relationship delete behavior, see Cascade Delete.
When to Use Cascade Delete
Use cascade delete when dependent data should not exist without the principal entity.
Common examples include:
- deleting order lines when an order is deleted
- deleting product images when a product is deleted
- deleting child records that only exist as part of a parent record
- deleting records that have no independent meaning without the principal entity
In the Category / Product example, cascade delete may be appropriate if products are not allowed to exist without a category.
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.Cascade);
This can keep the database consistent when products should not exist without their category.
When Not to Use Cascade Delete
Cascade delete is not always appropriate.
Avoid cascade delete when related data should be preserved.
For example, deleting a category should not always delete its products. In many systems, products may need to be moved to another category, archived, hidden, or reviewed before deleting the category.
Use a restrictive behavior when you want the delete to fail until the related data is handled explicitly.
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.Restrict);
Use an optional relationship with SetNull when the dependent entity can remain valid without the principal.
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.SetNull);
Do not use cascade delete just because it makes deletes easier.
Use it only when automatic deletion matches the business rule.
Common Pitfalls
Be careful with the following mistakes.
Expecting Related Data to Always Be Deleted
Deleting a principal entity does not always delete related data.
The result depends on the configured relationship behavior and the database constraints.
context.Categories.Remove(category);
await context.SaveChangesAsync();
Depending on the configuration, this can delete products, set their foreign key to null, or fail because products still reference the category.
Using SetNull with a Required Relationship
DeleteBehavior.SetNull requires a nullable foreign key.
This is valid:
public int? CategoryId { get; set; }
This is not valid for SetNull:
public int CategoryId { get; set; }
If the foreign key column is not nullable, the database cannot set it to null.
Accidentally Deleting Too Much Data
Cascade delete can remove more rows than expected.
For example, deleting one category may delete every product in that category.
context.Categories.Remove(category);
await context.SaveChangesAsync();
Before using cascade delete, confirm that the dependent data has no independent value.
If the data should be preserved, use a restrictive behavior or move the dependents before deleting the principal.
Assuming Loaded and Unloaded Dependents Behave the Same
When dependents are loaded and tracked, EF Core can apply the configured behavior in memory before saving.
When dependents are not loaded, the database constraint becomes more important.
var category = await context.Categories
.SingleAsync(c => c.Name == "Peripherals");
context.Categories.Remove(category);
await context.SaveChangesAsync();
In this example, the products are not loaded.
If the database is not configured to cascade the delete, the operation can fail.
Ignoring Foreign Key Constraints
Cascade delete does not remove the need to understand foreign keys.
A dependent row must either:
- be deleted
- have its foreign key set to a valid value
- have its foreign key set to
null, if the relationship is optional
If none of those options is valid, the database can reject the delete.
External Resources - Cascade Delete
The following videos are part of the same EF Core relationship series and are useful if you want to see how different delete behaviors work in practical one-to-many scenarios.
Together, they cover cascade delete, no-action behavior, and set-null behavior.
Video 1 - S1P44: EFCore One to Many Relations Default Cascade Delete
Lars Bilde demonstrates a one-to-many EF Core relationship where deleting a principal entity can also delete related dependent entities. The video is useful for understanding the default cascade delete behavior, how EF Core configures relationships, and how ON DELETE CASCADE can appear in the generated database schema.
It fits especially well with the Category / Product examples in this article, where deleting a principal entity can affect related dependent rows through relationship delete behavior.
Key sections:
- 00:00 — Introduction to EF Core relationships and cascade delete behavior
- 00:30 — Practical example of deleting a principal entity and its effect on dependent entities
- 01:30 — Why understanding cascade delete behavior is important
- 02:30 — Summary of delete behavior configuration and cascade delete warnings
Video 2 - S1P45: EFCore One to Many Relations Default No Action Delete Behavior
This video focuses on a one-to-many EF Core relationship where related entities are not automatically deleted. It is useful for understanding how NoAction can prevent automatic cascade delete and why deleting a principal entity can fail when dependent rows still reference it.
Use this resource if you want to understand why restrictive delete behaviors are useful when related data should be reviewed, moved, or deleted explicitly before deleting the principal entity.
Key sections:
- 00:00 — Introduction to delete behavior in EF Core
- 00:30 — Example of configuring
DeleteBehavior.NoAction - 01:00 — Demonstration of how deleting a principal entity affects dependent entities
- 02:00 — Comparing
Cascade,Restrict, andNoAction
Video 3 - S1P46: EFCore ZeroOrOne to Many Relations Set Null Delete Behavior
This video focuses on DeleteBehavior.SetNull in EF Core relationships. It is useful for understanding how dependent rows can remain in the database when a principal entity is deleted, as long as the foreign key is nullable and the relationship is optional.
It complements the SetNull section of this article by showing how a relationship can keep dependent entities while clearing the foreign key value.
Key sections:
- 00:00 — Introduction to delete behaviors in EF Core
- 01:00 — Explanation of
SetNullbehavior and where it applies - 02:30 — Practical example of how
SetNullbehaves in code - 03:30 — Consequences of allowing nullable relationships
Summary
Cascade delete controls what happens to dependent entities when a related principal entity is deleted.
Key points:
Remove()does not delete rows immediatelySaveChangesAsync()sends the pending delete operation and lets the configured behavior take effectDeleteBehavior.Cascadecan delete related dependents automaticallyDeleteBehavior.RestrictorNoActioncan prevent automatic cascade deletesDeleteBehavior.SetNullcan keep dependents and set the foreign key tonullSetNullrequires an optional relationship with a nullable foreign key- EF Core can apply cascade behavior to tracked dependent entities
- the database can apply cascade behavior through foreign key constraints
- deleting a principal entity can fail when dependent rows still reference it
Use cascade delete when dependent data should not exist without the principal entity.
Use restrictive behavior when related data should be reviewed, moved, or deleted explicitly.
Use SetNull when dependent data can remain valid without the principal entity.
Related Articles
If you want to understand how cascade delete fits into the broader EF Core saving and relationship workflow, these pages are the best next steps:
- Deleting Data — how to delete entities with
Remove()andRemoveRange() - SaveChanges — how EF Core persists tracked changes
- ChangeTracker — how EF Core tracks entity states before saving
- One-to-Many Relationship Configuration — how to configure one-to-many relationships
- Referential Constraint Action Options — how relationship delete behaviors affect foreign key constraints
FAQ
What is cascade delete in EF Core?
Cascade delete means that deleting a principal entity can also delete its dependent entities.
For example, deleting a Category can also delete the related Product rows if the relationship is configured for cascade delete.
What happens to related data when I delete an entity?
It depends on the relationship configuration.
Related data can be deleted, have its foreign key set to null, or cause the delete operation to fail if database constraints would be violated.
What is the difference between Cascade, Restrict, NoAction, and SetNull?
Cascade can delete dependent entities when the principal entity is deleted.
Restrict or NoAction can prevent automatic cascade delete and can cause the delete to fail if dependent rows still reference the principal entity.
SetNull can set the dependent foreign key to null, but only when the relationship is optional and the foreign key is nullable.
Does EF Core or the database perform cascade delete?
Both are possible.
EF Core can apply cascade behavior to dependent entities that are loaded and tracked by the current DbContext.
The database can apply cascade behavior when the foreign key constraint is configured with cascade delete.
Why does deleting a principal entity fail?
The delete can fail if dependent rows still reference the principal entity and the relationship does not allow cascade delete or setting the foreign key to null.
In that case, the database rejects the delete to protect referential integrity.
When should I avoid cascade delete?
Avoid cascade delete when related data should be preserved, reviewed, moved, archived, or deleted explicitly.
Use cascade delete only when the dependent data should not exist without the principal entity.