Skip to main content

Fixed: "A second operation was started on this context instance before a previous operation completed" in EF Core

· 7 min read
Jagdish Kumawat
Founder @ Dewiride

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:

src/Dewiride.Inventory.Api/Program.cs (temporary endpoint)
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​

Response
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"
}
API log (the lines that matter)
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.

A terminal with a 500 response, and the API log with InvalidOperationException: A second operation was started on this context instance before a previous operation completed.

What I Checked​

Whether it fails every time. It doesn't. Another run of the same code answered this:

Response (the same code, another run)
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:

An endpoint that awaits each query
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();
Response
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:

An endpoint with one scope per query
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:

Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"skus": 8,
"names": 8,
"categories": 3
}

A terminal with a 200 OK response from the endpoint that awaits each query in turn, with the three counts it read.

Common Mistakes​

MistakeWhat happensDo this instead
Task.WhenAll over queries on one contextThis exception, some of the timeawait each one, or use separate contexts
Forgetting one awaitThe next query collides with itawait every EF Core call straight away
Trusting a passing testIt can pass by timing aloneRead the code for unawaited calls
Parallel.ForEach over a shared contextThe same exception, or worseOne context per unit of work
Registering the context as a singletonAll requests share it, so they collideKeep the default scoped lifetime

Troubleshooting​

SymptomLikely causeFix
The error appears now and then, under loadUnawaited calls, or a shared contextSearch for async calls with no await
The error appears in a background taskIt uses the request's contextCreate a scope in the task
The error appears in a foreach that runs a query per rowA query while another is being readRead the list first, with ToListAsync
The error appears in Blazor ServerOne context per circuit, shared by eventsUse 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​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.