Introduction
In this article, we will learn about EF Core Bulk Operations.
Before we start, please take a look at my last article on Entity Framework.
Now, let's get started.
In EF Core, normal AddRange() , Update() , and Remove() Works fine for small datasets, but when you deal with thousands/millions of rows, performance tanks because EF sends one SQL command per row.
For that, you need bulk operations.
1. Built-in Options in EF Core
EF Core itself doesn’t provide true bulk operations out of the box, but you can optimize :
AddRange() / UpdateRange() / RemoveRange() → fewer SaveChanges() calls, but still multiple SQL statements under the hood.
ExecuteUpdate() and ExecuteDelete() (EF Core 7+) → allow set-based operations :
// Bulk Update
await context.Orders
.Where(o => o.Status == "Pending")
.ExecuteUpdateAsync(s => s
.SetProperty(o => o.Status, "Completed")
.SetProperty(o => o.UpdatedAt, DateTime.UtcNow));
// Bulk Delete
await context.Orders
.Where(o => o.IsCancelled)
.ExecuteDeleteAsync();
These are translated into single SQL UPDATE/DELETE queries, so very fast.
2. Third-Party Libraries for True Bulk Operations
When you need bulk insert/update/delete/merge, use libraries:
(a) EFCore.BulkExtensions (popular, free & open source)
![EFCore_BulkExtension]()
using EFCore.BulkExtensions;
// Bulk Insert
await context.BulkInsertAsync(customers);
// Bulk Update
await context.BulkUpdateAsync(customers);
// Bulk Delete
await context.BulkDeleteAsync(customers);
// Bulk Merge (Insert or Update)
await context.BulkInsertOrUpdateAsync(customers);
✅ Works with SQL Server, PostgreSQL, MySQL, and SQLite.
✅ Supports batching, transactions, and temp tables.
(b) Z.EntityFramework.Extensions (paid, very powerful)
![Z_EF_Extension2]()
// Bulk Insert
context.BulkInsert(customers);
// Bulk Update
context.BulkUpdate(customers);
// Bulk Delete
context.BulkDelete(customers);
// Bulk Merge
context.BulkMerge(customers);
✅ Very fast (millions of rows).
✅ Supports audit logs, filters, batch size, and includes Graphs.
❌ Commercial license after trial.
3. Raw SQL for Maximum Control
If you don’t want dependencies, just run SQL:
await context.Database.ExecuteSqlRawAsync(
"UPDATE Orders SET Status = 'Completed' WHERE Status = 'Pending'");
✅ Best performance, no EF tracking overhead.
❌ Lose EF change tracking & compile-time safety.
When to Use What
< 1000 rows → normal AddRange/SaveChanges is fine.
10K–1M rows → use EFCore.BulkExtensions or ExecuteUpdate/Delete.
Extreme performance (millions+) → raw SQL or Z.EntityFramework.Extensions.
Conclusion
In this article, I have tried to cover EF Core Bulk Operations.