Efficiently managing bulk data operations—such as inserting or updating thousands of records—is critical for high-performance enterprise applications. While Entity Framework Core (EF Core) provides a powerful ORM abstraction, its default behavior is not optimized for bulk operations, as it tracks entities individually.
In this article, we’ll explore why EF Core struggles with bulk operations, practical strategies for improving performance, and code-based examples using both native EF Core techniques and third-party libraries.
1. Understanding the Problem with EF Core Bulk Operations
When you perform a typical insert or update in EF Core like this:
foreach (var item in items)
{
_context.MyEntities.Add(item);
}
await _context.SaveChangesAsync();
EF Core
Tracks each entity in memory.
Generates individual INSERT or UPDATE statements for each record.
Causes high memory usage and slow performance when processing thousands of entities.
This approach is fine for small datasets, but for large imports (e.g., log ingestion, batch imports, sync jobs), it quickly becomes inefficient.
2. Common Scenarios Requiring Bulk Operations
Importing data from Excel, CSV, or external APIs.
Synchronizing data between systems.
Archiving or restoring large datasets.
Batch updating transactional data or status fields.
3. Native EF Core Techniques for Performance Optimization
Even without third-party tools, you can optimize EF Core operations using a few key patterns.
a) Disable Change Tracking
Change tracking consumes memory. If you don’t need to track entities after saving, disable it:
_context.ChangeTracker.AutoDetectChangesEnabled = false;
foreach (var item in items)
{
_context.MyEntities.Add(item);
}
await _context.SaveChangesAsync();
_context.ChangeTracker.AutoDetectChangesEnabled = true;
This reduces EF’s overhead significantly during mass inserts.
b) Batch Processing with SaveChanges in Chunks
Split your data into manageable batches to avoid transaction overhead and memory spikes:
int batchSize = 500;
for (int i = 0; i < items.Count; i += batchSize)
{
var batch = items.Skip(i).Take(batchSize);
_context.MyEntities.AddRange(batch);
await _context.SaveChangesAsync();
_context.ChangeTracker.Clear();
}
Benefits
Reduces database round-trips.
Prevents the EF context from growing too large.
Improves memory utilization.
c) Use ExecuteSqlRaw for Raw SQL Bulk Operations
When performance is critical and EF Core tracking isn’t needed, use direct SQL statements:
await _context.Database.ExecuteSqlRawAsync(
"INSERT INTO MyEntity (Name, CreatedDate) VALUES (@p0, @p1)",
parameters: new[] { name, createdDate.ToString() });
This approach bypasses EF’s change tracker entirely and can handle bulk operations faster—but at the cost of losing EF’s abstraction.
4. Using Third-Party Libraries for True Bulk Operations
The best way to handle large datasets efficiently in EF Core is to use specialized libraries built for this purpose.a) EFCore.BulkExtensions
A widely used open-source library that supports BulkInsert, BulkUpdate, BulkDelete, and BulkMerge.
Install the NuGet package:
Install-Package EFCore.BulkExtensions
Example: Bulk Insert
using EFCore.BulkExtensions;
await _context.BulkInsertAsync(largeListOfEntities);

Join the conversation! Your thoughts help the community grow.