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.