DEV Community

Temitope Okelola
Temitope Okelola

Posted on

Everyone Using DbContext Concurrently Is Making This Mistake

I Googled it. I asked AI. I tried every Stack Overflow answer I could find. Nothing worked. Then I found the fix completely by accident — in a book I wasn't even reading for that reason.

Here's what was actually going on — explained simply enough that even if you're new to .NET, you'll get it immediately.

What I Was Trying to Do

I was working on a feature that needed to call two different API operations at the same time — concurrently. Run both, wait for both to finish, move on. Simple enough in theory.

Except the moment I did, my app threw this:

InvalidOperationException: A second operation was started on this context
instance before a previous operation completed. This is usually caused
by different threads using the same instance of DbContext.
Enter fullscreen mode Exit fullscreen mode

I stared at it. Searched for it. Tried things for hours. Nothing matched my exact situation. I almost scrapped the whole concurrent approach and just ran them one after the other.

Then completely by accident — reading something for an entirely different reason — I landed on exactly what was wrong. One page. That's all it took.

First, What Even Is DbContext?

If you're using EF Core to talk to your database in .NET, DbContext is the main object you work with. You use it to query data, save records, update things — everything database-related goes through it.

When you write something like:

var users = await _context.Users.ToListAsync();
Enter fullscreen mode Exit fullscreen mode

That _context is your DbContext.

Think of it like a shopping basket. You use it to collect items, track what's changed, then check out (save to the database). It's designed to be used by one person at a time.

The Problem: I Was Sharing One Basket Between Two People at the Same Time

When I ran two API operations concurrently, both of them were trying to use the same DbContext instance simultaneously.

EF Core does not allow this. At all.

It's not a bug — it's by design. DbContext is built to handle one operation at a time. The moment two things try to use it at the same time, it throws that error.

Here's exactly what I was doing:

// ❌ Looks fine. Completely breaks at runtime.
var task1 = _context.Orders.Where(o => o.UserId == userId).ToListAsync();
var task2 = _context.Products.Where(p => p.IsActive).ToListAsync();

await Task.WhenAll(task1, task2);
Enter fullscreen mode Exit fullscreen mode

Both tasks start at the same time. Both reach for _context. EF Core sees two operations hitting the same instance simultaneously and throws the exception.

Perfectly reasonable looking code. Completely broken behavior.

Why Does This Happen? (The Simple Explanation)

DbContext keeps track of a lot of things internally — what data it has loaded, what has changed, what needs to be saved. It maintains all of this as state in memory.

If two operations are modifying that state at the same time, things get corrupted. It's the same reason you wouldn't let two people edit the same Word document simultaneously without something like Google Docs coordinating it.

EF Core doesn't coordinate it. It just says: one at a time, please.

The Fix: Give Each Operation Its Own DbContext

Once you understand the problem, the solution makes complete sense.

Instead of sharing one DbContext between both operations, give each operation its own separate instance. Completely independent — they don't know about each other, they can't conflict.

To do this in .NET, you use something called IServiceScopeFactory. Don't let the name scare you — all it does is let you create a new isolated environment (called a scope) that comes with its own fresh DbContext.

Here's the fixed version:

// ✅ Each operation gets its own DbContext — no conflicts
private async Task<List<Order>> GetOrdersAsync(CancellationToken token)
{
    using var scope = _scopeFactory.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    return await context.Orders.Where(o => o.UserId == userId).ToListAsync(token);
}

private async Task<List<Product>> GetProductsAsync(CancellationToken token)
{
    using var scope = _scopeFactory.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    return await context.Products.Where(p => p.IsActive).ToListAsync(token);
}

// Now run them concurrently — completely safe
var task1 = GetOrdersAsync(stoppingToken);
var task2 = GetProductsAsync(stoppingToken);

await Task.WhenAll(task1, task2);
Enter fullscreen mode Exit fullscreen mode

Inject _scopeFactory into your class like this:

private readonly IServiceScopeFactory _scopeFactory;

public MyService(IServiceScopeFactory scopeFactory)
{
    _scopeFactory = scopeFactory;
}
Enter fullscreen mode Exit fullscreen mode

No special packages. No extra configuration. Fully built into .NET.


Breaking It Down Line by Line

using var scope = _scopeFactory.CreateScope();
Enter fullscreen mode Exit fullscreen mode

Creates a brand new isolated environment. Think of it as opening a fresh browser tab — nothing from the other tab carries over.

var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
Enter fullscreen mode Exit fullscreen mode

Gets a fresh DbContext that belongs exclusively to this scope. It has zero knowledge of what the other operation is doing.

The using keyword at the start means when the method finishes, the scope automatically cleans itself up. The DbContext it created gets properly disposed. You don't have to do anything extra.

Why Stack Overflow and AI Couldn't Help Me

Most answers online assume you're hitting this error inside a background service — a very common place this same problem appears. My situation was different: two concurrent API calls inside regular application logic. Same error, different context, different search terms. That's why nothing was showing up.

If you're not searching with exactly the right words, you'll scroll right past the answer without recognising it.

The One Thing to Remember

DbContext is not built for sharing. The moment two operations run concurrently and reach for the same instance, you'll hit this error.

The rule is simple: one concurrent operation, one DbContext. Use IServiceScopeFactory to create a fresh one for each and you'll never see that exception again.

Sometimes the answer isn't on the first page of Google. Keep digging — it's always somewhere.

Has this ever happened to you — spending hours searching only to find the answer somewhere completely unexpected? Drop it in the comments.

Top comments (0)