What is ExecuteDelete?
ExecuteDelete is an EF Core feature introduced in EF Core 7 (see also ExecuteUpdate). It allows you to delete database rows directly in SQL, without loading entities into memory, without using the Change Tracker, and without calling SaveChanges.
Instead of materializing entities and calling RemoveRange followed by SaveChanges, you define the delete operation using a LINQ query that EF Core translates into a single DELETE statement executed on the database server.
ExecuteDelete is available in both synchronous and asynchronous forms: ExecuteDelete and ExecuteDeleteAsync.
Basic example:
await context.Authors
.Where(a => a.Name.Contains("ZZZ Projects"))
.ExecuteDeleteAsync();
Generated SQL:
DELETE FROM [a]
FROM [Authors] AS [a]
WHERE [a].[Name] LIKE N'%ZZZ Projects%'
In short, it tells the database: “delete all rows matching this condition.”
This makes deletes dramatically faster and is ideal for bulk or set-based operations.
Tip: Always start with a filter and validate the generated SQL, especially in production.
ExecuteDelete Requirements
- EF Core Version: EF Core 7.0+
- Supported Providers: SQL Server, SQLite, PostgreSQL, MySQL, Oracle
- Unsupported Providers: MariaDB, InMemory
TL;DR – ExecuteDelete
- Runs set-based deletes directly in SQL.
- Executes immediately (does not require calling
SaveChanges). - Does not load entities into memory (massive performance boost).
- Does not use or update the Change Tracker.
- Database cascades (ON DELETE CASCADE) still apply if configured
- Requires deleting rows in the correct order to avoid foreign key violations.
- EF Extensions supports real bulk deletes for entity-based or large-scale delete scenarios.
A quick mental model
ExecuteDelete= “Tell the database: delete all rows matching this condition.” Ideal for uniform delete rules (the same rule applied to every matched row).BulkDelete(EF Extensions) = “Send a specific list of entity keys to the database and delete exactly those rows efficiently.” Essential when deleting specific entities, working with very large datasets, or requiring advanced control.
When to use vs when NOT to use
Use ExecuteDelete when… |
Avoid ExecuteDelete when… |
|---|---|
| The delete rule is uniform for all matching rows | You need to delete a specific list of entities from memory |
| You want one SQL DELETE and minimal round-trips | You need a high-throughput pipeline (staging + DELETE JOIN) |
| You want a zero-dependency EF Core solution | You need graph deletes (related entities) |
| You’re doing cleanup or maintenance deletes | You need auditing, batching knobs, orchestration features |
| You are OK managing FK order yourself | You require entity-based control and cascade handling |
For scenarios that ExecuteDelete does not support—such as deleting a specific list of entities coming from memory, very large datasets, or advanced orchestration—you should use BulkDelete (EF Extensions) for a fast and efficient bulk delete pipeline.
Important:
ExecuteDeleteis not a replacement for BulkDelete. They solve different classes of problems and are complementary.
ExecuteDelete Examples
Delete all rows (use with caution)
This example shows how to call ExecuteDelete on a DbSet.
It immediately deletes all rows from the table.
using (var context = new LibraryContext())
{
await context.Authors.ExecuteDeleteAsync();
}
Generated SQL:
DELETE FROM [a]
FROM [Authors] AS [a]
Delete rows with a filter
You can delete rows matching a predicate.
This example deletes authors whose name contains "ZZZ Projects".
using (var context = new LibraryContext())
{
await context.Authors
.Where(a => a.Name.Contains("ZZZ Projects"))
.ExecuteDeleteAsync();
}
Generated SQL:
DELETE FROM [a]
FROM [Authors] AS [a]
WHERE [a].[Name] LIKE N'%ZZZ Projects%'
Delete rows with a complex filter (navigation filter)
More complex filters are supported, including filters based on related data.
This example deletes tags only for posts published before 2018:
using (var context = new BloggingContext())
{
await context.Tags
.Where(t => t.Posts.All(p => p.PublishedOn.Year < 2018))
.ExecuteDeleteAsync();
}
Generated SQL:
DELETE FROM [t]
FROM [Tags] AS [t]
WHERE NOT EXISTS (
SELECT 1
FROM [PostTag] AS [p]
INNER JOIN [Posts] AS [p0] ON [p].[PostsId] = [p0].[Id]
WHERE [t].[Id] = [p].[TagsId]
AND NOT (DATEPART(year, [p0].[PublishedOn]) < 2018)
)
Return the number of rows affected
ExecuteDelete returns the number of rows deleted.
using (var context = new LibraryContext())
{
var rowsDeleted = await context.Authors
.Where(a => a.Name.Contains("ZZZ Projects"))
.ExecuteDeleteAsync();
Console.WriteLine($"Rows deleted: {rowsDeleted}");
}
ExecuteDelete Release History
- EF Core 8.0: Improved
ExecuteUpdateandExecuteDeleteto support more complex queries (owned types, unions, and TPT), as long as all operations target a single database table. - EF Core 7.0: Introduced
ExecuteUpdateandExecuteDeleteas native set-based operations in EF Core. - EF Core 2.0+: For older versions of EF Core, or for unsupported providers, you can use DeleteFromQuery (Entity Framework Extensions).
Is ExecuteDelete a real “Bulk Delete”?
Short answer: not quite.
Yes, it deletes rows in bulk directly in SQL. However, it does not delete entities from a list with per-row control like BulkDelete (Entity Framework Extensions).
The difference is simple:
ExecuteDelete: “Delete all rows matching this condition.”BulkDelete: “Delete exactly these entities.”
// ExecuteDelete
await context.Customers
.Where(c => c.Age > 30)
.ExecuteDeleteAsync();
// BulkDelete (with EF Extensions)
await context.BulkDeleteAsync(customers);
What’s the practical difference?
| Question | ExecuteDelete (EF Core) |
BulkDelete (EF Extensions) |
|---|---|---|
| How are rows selected? | One LINQ rule for all matching rows | Explicit list of entities |
| Where does the data come from? | LINQ expression translated to SQL | In-memory entity list |
| How many rows? | Thousands to hundreds of thousands | Designed for very large volumes |
| Related entities supported? | ❌ No | ✅ Yes (IncludeGraph) |
| Change Tracker sync? | ❌ No | ✅ Yes (With option) |
Performance Benchmarks
Deleting 100,000 rows:
| Method | Time | Memory |
|---|---|---|
| ExecuteDelete | 200 ms | Very low |
| BulkDelete (EF Extensions) | 1050 ms | Low |
| SaveChanges | 2250 ms | High |
Important: For
BulkDeleteandSaveChanges, around 250 ms of this time is spent materializing entities from the database before the delete starts. This is required because both approaches operate from entities, not from a LINQ rule.
Takeaway: ExecuteDelete can be an order of magnitude faster than the traditional tracked approach, while memory usage stays near-constant because entities are never loaded.
Bulk Delete – Concrete Example
Suppose you need to delete 1,000,000 customers by ID coming from a CSV import.
With ExecuteDelete, you would need to loop in chunks, manually build filters, or stage the data yourself.
With EF Extensions:
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
var customersToDelete = GetCustomersFromCsv();
context.BulkDelete(customersToDelete, options =>
{
options.BatchSize = 10000;
});
Behind the scenes, EF Extensions sends entity keys to a temporary table and executes a high-throughput DELETE JOIN — something ExecuteDelete cannot do on its own.
When to use which?
| Scenario | Recommended |
|---|---|
| Same delete rule for all matching rows | ExecuteDelete |
| Delete rows using a LINQ filter | ExecuteDelete |
| Delete a specific list of entities | EF Extensions BulkDelete |
| Large-scale deletes | ExecuteDelete or EF Extensions BulkDelete |
| Need graph deletes, batching, or auditing | EF Extensions BulkDelete |
| Need entity-based control | EF Extensions BulkDelete |
Use
ExecuteDeletewhen:- All rows can be deleted using the same filter condition
- You want a native EF Core feature with no additional dependency
- The dataset is small to medium and rule-based
- You want one SQL DELETE with minimal round-trips
Use a real bulk delete (Entity Framework Extensions) when:
- You need to delete a specific list of entities
- Deletion is driven by external or in-memory data
- You’re deleting hundreds of thousands to millions of rows
- You need graph deletes, batching, auditing, or logging
Bottom line:
ExecuteDeleteis a fast set-based delete tool.
A real bulk delete is a high-throughput entity-based pipeline built for scale, per-row control, and advanced orchestration.
They complement each other rather than compete.
Additional Resources – ExecuteDelete
📘 Recommended Reading
🎥 Recommended Videos
Entity Framework 7 – Bulk Editing
by @CodingTutorialsAreGo (Jasper Kent)
In this video, Jasper introduces the new ExecuteDelete feature added in EF Core 7. He walks you through every step—from how deletions were traditionally performed to replacing them with the new method for both single and multiple entities. He also compares the SQL generated, explains why calling SaveChanges is no longer required, and shows how to retrieve the number of affected rows.
He covers everything from project setup, traditional pitfalls, and how this new method improves performance and code clarity.
Key timestamps:
- 00:00 - Introduction – Overview of
ExecuteDeleteandExecuteUpdate. - 00:34 - Project Setup – Quick explanation of the demo project.
- 02:09 - Traditional Way to Delete – Shows why the old method is inefficient.
- 07:19 - Implementing ExecuteDelete – Demo of the new method and SQL comparison.
- 24:35 - Conclusion and Performance Insights – Final thoughts on how game-changing these new methods are.
You can download the project source code here: Ef7-Bulk-Actions
Core 7 – Performance Improvements With the New ExecuteUpdate & ExecuteDelete, por Milan Jovanović
In the final section, Milan shows how ExecuteDelete removes multiple records in a single SQL DELETE without loading entities. He demonstrates a conditional delete, the generated SQL, and the efficiency compared to the classic deletion loop.
Key timestamps:
- 07:00 – Building the delete endpoint using ExecuteDelete
- 08:25 – Traditional delete pattern and why it’s inefficient
- 08:45 – Running ExecuteDelete with a conditional filter
- 09:30 – Observing the generated SQL
- 10:15 – SQL DELETE produced by EF Core 7
- 10:45 – Final explanation of the direct “range delete” behavior
Entity Framework 7 Makes Performing UPDATES and DELETES Easy!, por Israel Quiroz
Israel demonstrates how ExecuteDeleteAsync removes entities directly in the database using a filtered query—without retrieving them first. He compares the traditional 3-step delete process with the new single-roundtrip method and highlights the returned count of affected rows.
Key timestamps:
- 01:40 – Traditional delete workflow (Find → Remove → SaveChanges)
- 02:00 – Replacing the workflow with ExecuteDeleteAsync
- 02:15 – Returned value: number of deleted rows
- 02:40 – One database round-trip vs multiple calls
- 04:40 – Runs without loading or tracking entities
🎥 (SPANISH) Borrado y Actualizaciones Masivas – Nuevo de EF Core 7, por Felipe Gavilan
Felipe shows how ExecuteDelete removes thousands of rows directly in SQL Server without loading entities. He demonstrates a full-table delete, a filtered delete, and a performance comparison with RemoveRange.
Key timestamps:
- 01:47 – Full-table delete using ExecuteDelete
- 03:16 – Conditional delete using Where(...).ExecuteDelete()
- 04:32 – Verifying filtered results in SQL Server
- 08:31 – Starting stopwatch for performance comparison
- 09:31 – Final numbers: 100k deletes (8s → 0.2s)
Do You Know The Fastest Way To Delete Data With EF Core?, por Milan Jovanović
Milan compares three delete approaches and shows why ExecuteDeleteAsync is the most performant. He highlights SQL generation, tracking behavior, concurrency issues, and real-world cleanup scenarios.
Key timestamps:
- 00:45 – Traditional delete workflow (Find → Remove → SaveChanges)
- 03:00 – Second approach: Attach + EntityState.Deleted (concurrency issue)
- 08:32 – Introducing ExecuteDeleteAsync
- 08:45 – Demo: one SQL query, no tracking
- 09:15 – Final conclusion: most performant delete method
Summary & Next Steps – ExecuteDelete
ExecuteDelete is a powerful EF Core feature that enables fast, set-based deletes without loading entities, without Change Tracker overhead, and without calling SaveChanges. It is ideal for cleanup operations and uniform deletes.
Use ExecuteDelete when:
- You want to delete rows using a single filter condition
- You want maximum performance without loading entities into memory
- You prefer a native EF Core solution with no extra dependency
Use EF Extensions BulkDelete when:
- You need to delete a specific list of entities
- You are working with hundreds of thousands or millions of rows
- You need advanced features like graph deletes, batching, or auditing
Next steps:
- Explore the EF Extensions BulkDelete documentation
- Learn about ExecuteUpdate