Insane Performance Boost in EF Core using Entity Framework Extensions

ZZZ Projects' EF Core Extensions Struggling with slow EF Core operations? Boost performance like never before. Experience up to 14× faster Bulk Insert, Update, Delete, and Merge - and cut your save time by as much as 94%. Learn more →
Introduction
Imagine that you've built a great application using .NET and Entity Framework Core. You start to process thousands - or tens of thousands - of records at once, and you notice a huge bottleneck: your application is getting slower and slower.
Standard EF Core is like a delivery driver who takes one package at a time to the same house, driving back and forth a thousand times. Entity Framework Extensions is the freight truck that delivers all 1,000 packages in a single trip.
In this article I'll show you how to use the Entity Framework Extensions library by ZZZ Projects to significantly improve the performance of your .NET application. We'll compare standard EF Core and EF Extensions on real use-case scenarios with actual benchmark numbers.
Here's what we'll cover:
- Installing the EF Extensions NuGet package
- Why EF Core slows down at scale
- BulkInsert, BulkInsertOptimized
- BulkUpdate, BulkDelete
- BulkMerge (upsert)
- BulkSynchronize (upsert + delete missing)
- BulkSaveChanges
🎬 Watch the full video here:
Why Standard EF Core Struggles at Scale
EF Core's SaveChanges() is convenient - it tracks entity state and handles inserts, updates, and deletes automatically. But it generates one SQL statement per entity. Insert 10,000 products? That's 10,000 round trips.
There are two reasons this kills performance:
1. The change tracker becomes a bottleneck. Tracking the state of 50,000 objects in memory consumes massive amounts of CPU and RAM. Entity Framework Extensions allows you to process data without the overhead of tracking every single property change - freeing your server to handle actual logic instead of managing memory.
2. Too many database round trips. You can see it directly in the console: EF Core floods it with individual INSERT statements, one per record. EF Extensions reduces all of those into a single bulk operation.
Installing Entity Framework Extensions
dotnet add package Z.EntityFramework.Extensions.EFCore
Or search for Z.EntityFramework.Extensions.EFCore directly in the Visual Studio NuGet package manager.
The package supports .NET 10 and works with:
- SQL Server
- PostgreSQL
- MySQL
- Oracle
- SQLite
Full docs, online benchmarks, and live examples are available at entityframework-extensions.net.
Licensing: A free trial is available so you can evaluate the library end-to-end. For commercial projects, a paid license is required from ZZZ Projects. Check the pricing page once you've benchmarked it against your own workloads.
Benchmarks at a Glance
All results below are measured on 10,000 records against a PostgreSQL database.
| Operation | EF Core | EF Extensions | Speedup |
|---|---|---|---|
| Insert | ~2,800 ms | 177 ms | ~16x |
| Insert Optimized | ~2,800 ms | 72 ms | ~39x |
| Update | ~1,500 ms | 100 ms | ~15x |
| Delete | ~800 ms | 31 ms | ~26x |
| Merge (upsert) | ~1,800 ms | ~100 ms | ~18x |
| Synchronize | ~1,700 ms | 126 ms | ~13x |
| BulkSaveChanges | ~1,400 ms | 425 ms | ~3x |
⚡ Bulk Insert
EF Core (standard):
await context.Products.AddRangeAsync(products);
await context.SaveChangesAsync();
EF Extensions:
await context.BulkInsertAsync(products);
Result: ~2,800 ms - 177 ms.
Need even more speed and don't need output values (like generated IDs) back? Use the optimized variant:
await context.BulkInsertOptimizedAsync(products);
This skips the output value pass-back entirely, dropping the operation to 72 ms - roughly 39x faster than standard EF Core.
Bulk Update
EF Core:
foreach (var p in products)
p.Price *= 1.10m;
await context.SaveChangesAsync();
EF Extensions:
foreach (var p in products)
p.Price *= 1.10m;
await context.BulkUpdateAsync(products);
Result: ~1,500 ms - 100 ms - 15x faster.
Bulk Delete
EF Core:
context.Products.RemoveRange(products);
await context.SaveChangesAsync();
EF Extensions:
await context.BulkDeleteAsync(products);
Result: ~800 ms - 31 ms - over 25x faster.
Bulk Merge (Upsert)
A merge inserts new records and updates existing ones in a single operation. EF Extensions automatically detects what needs to be inserted vs. updated.
EF Core (manual):
foreach (var p in existing)
p.Price *= 1.15m;
context.Products.AddRange(incoming);
await context.SaveChangesAsync();
EF Extensions:
foreach (var p in existing)
p.Price *= 1.15m;
var all = existing.Concat(incoming).ToList();
await context.BulkMergeAsync(all);
Result (5,000 updates + 5,000 inserts = 15,000 total): ~1,800 ms - ~100 ms.
Bulk Synchronize
Synchronize goes one step further than Merge - it also deletes records that are not in your source list:
- Rows that match the entity key are updated
- Rows in the source but not in the database are inserted
- Rows in the database but not in the source are deleted
Think of it as mirroring your source to the database.
EF Core (manual - a lot of code to get right):
var existingIds = source.Where(p => p.Id > 0).Select(p => p.Id).ToHashSet();
var toDelete = await context.Products
.Where(p => !existingIds.Contains(p.Id)).ToListAsync();
context.Products.RemoveRange(toDelete);
foreach (var p in source.Where(p => p.Id > 0))
context.Products.Update(p);
context.Products.AddRange(source.Where(p => p.Id == 0));
await context.SaveChangesAsync();
EF Extensions:
await context.BulkSynchronizeAsync(source);
Result: ~1,700 ms - 126 ms - and a fraction of the code.
Bulk SaveChanges
Already using the EF Core change tracker with a mix of Add, Update, and Delete? Drop in BulkSaveChangesAsync() as a near-zero-friction replacement:
await context.BulkSaveChangesAsync(); // instead of SaveChangesAsync()
This is the easiest migration path if you have existing code and just want a performance lift.
Result on a mixed Add/Update/Delete workload: ~1,400 ms - 425 ms.
When Should You Reach for This?
You don't need bulk operations for typical CRUD in a web app handling a handful of records at a time. But if any of these apply, the standard EF Core approach will hurt:
- Importing data from CSVs or external feeds
- Syncing records between systems
- Running nightly price or inventory updates across thousands of rows
- Handling data migrations
A one-line swap to BulkInsertAsync, BulkMergeAsync, or BulkSynchronizeAsync makes a real difference.
Key Takeaways
BulkInsertAsyncreplacesAddRangeAsync+SaveChangesAsync, up to 16x faster.BulkInsertOptimizedAsyncskips output values for maximum insert speed (~40x).BulkUpdateAsyncreplaces foreach +SaveChangesAsync, 15x faster.BulkDeleteAsyncreplacesRemoveRange+SaveChangesAsync, 25x faster.BulkMergeAsynchandles upsert in one call, auto-detects insert vs. update.BulkSynchronizeAsyncadds deletion of records missing from the source on top of upsert.BulkSaveChangesAsyncis a drop-in replacement forSaveChangesAsyncwith the change tracker.- A free trial is available - a commercial license is required for production use.
What's Next?
If you found this useful, the next step is benchmarking these operations against your own database and workload size. Subscribe to get more weekly .NET content and never miss a performance tip.