📘 .NET / Entity Framework Core
A second operation was started on this context before a previous operation completed
Why it happens, how to fix it, and how to avoid it forever. Complete guide with code examples for EF Core 6/7/8.
🔍 What is this error?
System.InvalidOperationException
“A second operation was started on this context before a previous operation completed.”
This exception is thrown by Entity Framework Core when you try to start a new database operation
(e.g., SaveChangesAsync(), ToListAsync(), FirstOrDefaultAsync())
on a DbContext instance while another asynchronous operation is still in progress
on the same context instance.
⚡ Why does it happen?
The underlying cause is almost always one of these:
- Missing
await– You called an async method but forgot to await it, causing the next operation to start before the previous one finishes. - Parallel execution on the same DbContext – Using
Task.WhenAllorParallel.ForEachwith the same context instance. - Reusing a context in a loop – Starting a new async operation inside a loop without waiting for the previous one.
- Concurrent requests in a web application – If you register DbContext as a singleton (instead of scoped), multiple requests share the same instance and cause concurrency.
In all cases, the solution is to ensure that only one operation uses the DbContext at any given time.
🧩 Common scenarios (with code)
1️⃣ Missing await
You call an async method but ignore the returned task:
// ❌ BAD – missing await
var task = context.Products.ToListAsync();
var count = context.Products.Count(); // ⚡ Error! Previous operation still running.
// ✅ GOOD – await the async call
var products = await context.Products.ToListAsync();
var count = context.Products.Count(); // Now safe – previous operation completed.
2️⃣ Parallel operations with Task.WhenAll
// ❌ BAD – using the same context in parallel
var task1 = context.Orders.ToListAsync();
var task2 = context.Customers.ToListAsync();
await Task.WhenAll(task1, task2); // ⚡ Error! Both start on same context.
// ✅ GOOD – use separate contexts or sequential await
// Option A: Sequential
var orders = await context.Orders.ToListAsync();
var customers = await context.Customers.ToListAsync();
// Option B: Separate contexts (if truly parallel)
using var ctx1 = new AppDbContext();
using var ctx2 = new AppDbContext();
var task1 = ctx1.Orders.ToListAsync();
var task2 = ctx2.Customers.ToListAsync();
await Task.WhenAll(task1, task2);
3️⃣ Loop with async operations
// ❌ BAD – starting new operation before previous finishes
foreach (var id in ids)
{
var entity = await context.Entities.FindAsync(id); // 🟢 first await is fine
// but if you start another async operation inside the same loop iteration,
// or start the next iteration without awaiting the previous one – you'll get the error.
}
Usually the error appears when you accidentally call SaveChangesAsync inside the loop
without awaiting properly, or when you use Task.Run.
The fix: Always await every async call, and consider batching
operations to reduce round-trips.
✅ How to fix it – complete solutions
🔹 1. Always await your async methods
Use the await keyword consistently. If you must fire-and-forget (rare), use a separate
scope or background service.
🔹 2. Use a new DbContext instance for parallel operations
If you need to run multiple queries concurrently, create a separate context for each parallel branch.
using var ctx1 = new AppDbContext();
using var ctx2 = new AppDbContext();
var ordersTask = ctx1.Orders.ToListAsync();
var customersTask = ctx2.Customers.ToListAsync();
await Task.WhenAll(ordersTask, customersTask);
🔹 3. Register DbContext as scoped in DI (not singleton)
In ASP.NET Core, the default registration AddDbContext registers DbContext as scoped
(per request). If you override it to singleton, multiple requests will share the same instance and
cause this error.
// ✅ CORRECT (default)
builder.Services.AddDbContext<AppDbContext>(options => ...);
// ❌ AVOID this unless you have a very specific reason
builder.Services.AddSingleton<AppDbContext>(...);
🔹 4. Use AsNoTracking() for read‑only queries
This reduces overhead, but it doesn't directly solve concurrency – it just makes queries faster and reduces chance of change‑tracking conflicts.
🔹 5. Consider synchronization if you must share a context
You can use a SemaphoreSlim to serialize access, but this is generally an anti‑pattern.
Better to use separate contexts.
private static readonly SemaphoreSlim _semaphore = new(1, 1);
public async Task<T> ExecuteSafelyAsync(Func<AppDbContext, Task<T>> operation)
{
await _semaphore.WaitAsync();
try
{
return await operation(_context);
}
finally
{
_semaphore.Release();
}
}
But prefer the simpler approaches above.
📌 Best practices to avoid this error
- Scope your DbContext appropriately – in web apps, use the default scoped lifetime (per request).
- Always
awaitasync calls – never ignore the returnedTask. - Use separate context instances for parallel work – don't share a context across threads.
- Avoid long‑running operations – if you have a long-running query, consider
splitting it or using
AsSplitQuery()to reduce complexity. - Leverage dependency injection – let the DI container manage your DbContext lifetime.
- Use
usingblocks orawait usingto ensure contexts are disposed properly.
📖 Summary
The error “A second operation was started on this context before a previous operation completed”
is a sign that your code is attempting to execute two asynchronous database operations on the same
DbContext instance before the first one finishes.
Fix it by:
- Always using
awaiton async methods. - Not sharing a DbContext across parallel tasks – use separate instances.
- Registering DbContext as scoped in DI.
- Designing your data access layer to be synchronous‑only per context instance.
Following these patterns will keep your EF Core code robust, performant, and free of this common exception.
❓ Have a specific scenario? Share it in the comments or reach out – we're happy to help.
0 Comments
thanks for your comments!