Fixed: "A second operation was started on this context instance before a previous operation completed" in EF Core
To save time, I started three EF Core queries together and waited for them with Task.WhenAll. The API answered 500: A second operation was started on this context instance before a previous operation completed. Then I ran the same code again, and it worked. A DbContext can only do one thing at a time, and when you break that rule, whether you notice depends on timing.
What I Was Doing
An endpoint in a .NET 10 Web API needed three independent lists, so I started them at the same time on the InventoryDbContext it was given:
app.MapGet("/demo/parallel", async (InventoryDbContext db) =>
{
var skus = db.Products.Select(p => p.Sku).ToListAsync();
var names = db.Products.Select(p => p.Name).ToListAsync();
var categories = db.Categories.Select(c => c.Name).ToListAsync();
await Task.WhenAll(skus, names, categories);
return Results.Ok(new { skus = skus.Result.Count, names = names.Result.Count, categories = categories.Result.Count });
});
What I Expected
200 OK, three counts, and a faster answer than running the queries one after another.
What I Got
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "An error occurred while processing your request.",
"status": 500,
"traceId": "00-14e8ff45b4ef68babad0918b38439379-cef4c960689edfab-00"
}
fail: Microsoft.EntityFrameworkCore.Query[10100]
An exception occurred while iterating over the results of a query for context type 'Dewiride.Inventory.Data.InventoryDbContext'.
System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

What I Checked
Whether it fails every time. It doesn't. Another run of the same code answered this:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"skus": 8,
"names": 8,
"categories": 3
}
That's the worst part: tests can pass, and it fails later under load, because it depends on whether one query finishes before the next one starts.
What the documentation says. EF Core's DbContext lifetime page is clear: "Entity Framework Core does not support multiple parallel operations being run on the same DbContext instance. This includes both parallel execution of async queries and any explicit concurrent use from multiple threads. Therefore, always await async calls immediately, or use separate DbContext instances for operations that execute in parallel." It adds a warning about what happens when EF Core doesn't catch it: "When concurrent access goes undetected, it can result in undefined behavior, application crashes and data corruption."
How the code breaks that rule. ToListAsync starts its work and returns a task. Calling it three times without await starts three operations on one context.
Why It Happens
A DbContext holds one database connection and one change tracker, and neither is safe to use from two operations at once. EF Core has a small check that notices when a second operation starts during another. That check is what threw here. It's a best-effort check, though, and only trips when the timing overlaps.
The Fix
Run them one after another
This is the right answer for most requests. await each one straight away:
var skus = await db.Products.Select(p => p.Sku).ToListAsync();
var names = await db.Products.Select(p => p.Name).ToListAsync();
var categories = await db.Categories.Select(c => c.Name).ToListAsync();
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"skus": 8,
"names": 8,
"categories": 3
}
Or give each query its own context
If you measured and really need them in parallel, each one needs its own DbContext, from its own scope. IServiceScopeFactory creates scopes:
app.MapGet("/demo/parallel-scopes", async (IServiceScopeFactory scopes) =>
{
async Task<List<string>> ReadAsync(Func<InventoryDbContext, Task<List<string>>> read)
{
await using var scope = scopes.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<InventoryDbContext>();
return await read(db);
}
var skus = ReadAsync(db => db.Products.Select(p => p.Sku).ToListAsync());
var names = ReadAsync(db => db.Products.Select(p => p.Name).ToListAsync());
var categories = ReadAsync(db => db.Categories.Select(c => c.Name).ToListAsync());
await Task.WhenAll(skus, names, categories);
return Results.Ok(new { skus = skus.Result.Count, names = names.Result.Count, categories = categories.Result.Count });
});
Each query then uses its own connection from the pool, which costs more than it looks. For three small queries, one after another is usually just as fast.
After the Fix
Both versions answered 200 OK, on every run:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"skus": 8,
"names": 8,
"categories": 3
}

Common Mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
Task.WhenAll over queries on one context | This exception, some of the time | await each one, or use separate contexts |
Forgetting one await | The next query collides with it | await every EF Core call straight away |
| Trusting a passing test | It can pass by timing alone | Read the code for unawaited calls |
Parallel.ForEach over a shared context | The same exception, or worse | One context per unit of work |
| Registering the context as a singleton | All requests share it, so they collide | Keep the default scoped lifetime |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The error appears now and then, under load | Unawaited calls, or a shared context | Search for async calls with no await |
| The error appears in a background task | It uses the request's context | Create a scope in the task |
The error appears in a foreach that runs a query per row | A query while another is being read | Read the list first, with ToListAsync |
| The error appears in Blazor Server | One context per circuit, shared by events | Use IDbContextFactory |
FAQ
Why is it fine in most requests?
Each request gets its own DbContext, and one request usually runs one thing at a time. The DbContext lifetime page says this is "safe from concurrent access issues in most ASP.NET Core applications".
Is the parallel version really faster?
Not automatically. Each query takes a connection from the pool, and MySQL does the work either way. Measure before you choose it.
Conclusion
"A second operation was started on this context instance" means two operations overlapped on one DbContext, which EF Core doesn't support. It depends on timing, so the same code can pass one run and fail the next. await each query straight away, or give each parallel query its own context from IServiceScopeFactory.
Additional Resources
- Avoiding DbContext threading issues — the rules and the common mistakes.
- Asynchronous Programming in EF Core — using async queries correctly.
