Skip to main content

Fixed: "Cannot access a disposed context instance" in EF Core

· 6 min read
Jagdish Kumawat
Founder @ Dewiride

An endpoint started some work in the background and answered 200 OK straight away. Half a second later the log said ObjectDisposedException: Cannot access a disposed context instance. The DbContext belongs to the request: when the request ended, ASP.NET Core disposed of it, and the background task was still holding it. Background work needs a context of its own.

What I Was Doing​

In a .NET 10 Web API, an endpoint started a task that counts products, so it could answer without waiting. The task used the InventoryDbContext the endpoint was given:

src/Dewiride.Inventory.Api/Program.cs (temporary endpoint)
app.MapGet("/demo/disposed", (InventoryDbContext db, ILoggerFactory loggers) =>
{
var logger = loggers.CreateLogger("Demo");
_ = Task.Run(async () =>
{
await Task.Delay(500);
try
{
var count = await db.Products.CountAsync();
DemoLog.Counted(logger, count);
}
catch (ObjectDisposedException exception)
{
DemoLog.BackgroundFailed(logger, exception);
}
});
return Results.Ok(new { started = true });
});

What I Expected​

200 OK, and half a second later a log line with the number of products.

What I Got​

The response looked fine:

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

{
"started": true
}

The log didn't:

API log (the lines that matter)
fail: Demo[1568076500]
Background work failed
System.ObjectDisposedException: Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'InventoryDbContext'.

A terminal with a 200 OK response from the endpoint, and half a second later the API log with ObjectDisposedException: Cannot access a disposed context instance, Object name: 'InventoryDbContext'.

Without the try/catch, nothing would have appeared at all, because nobody is waiting for that task.

What I Checked​

Who disposes of the context. I never called Dispose. EF Core's DbContext lifetime page says AddDbContext "registers DbContext types with a scoped lifetime by default", and that "each request gets a separate dependency injection scope (and therefore a separate DbContext instance)". A scoped service is disposed of when its scope ends, and the request's scope ends with the request.

When that happens compared with my task. The endpoint returned at once, and the task waited 500 milliseconds. So the request had finished long before CountAsync ran.

What the message suggests. It spells it out: "A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application."

Why It Happens​

Dependency injection hands out scoped services for the length of the request. Nothing stops your code from keeping a reference after that. A task started with Task.Run, a method marked async void, or a query you forgot to await can all outlive the request. When they touch the context afterwards, it's already disposed of.

The Fix​

Give the background work its own scope. IServiceScopeFactory is a singleton, so it's safe to keep, and each scope gets a new DbContext:

An endpoint that gives the task its own scope
app.MapGet("/demo/background-scope", (IServiceScopeFactory scopes, ILoggerFactory loggers) =>
{
var logger = loggers.CreateLogger("Demo");
_ = Task.Run(async () =>
{
await Task.Delay(500);
await using var scope = scopes.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<InventoryDbContext>();
var count = await db.Products.CountAsync();
DemoLog.Counted(logger, count);
});
return Results.Ok(new { started = true });
});

await using disposes of the scope and its context when the task is done. For work that matters, use a proper hosted service with a queue, because a Task.Run like this is lost if the app stops.

After the Fix​

API log
info: Demo[233888763]
Counted 8 products

A terminal with the API log half a second after the response: the line Counted 8 products, from the background work with its own scope.

Common Mistakes​

MistakeWhat happensDo this instead
Using the request's context in Task.RunThis exception, after the response has goneCreate a scope in the task
Forgetting await on a queryThe request can end first, with the same resultawait every call
Marking a method async voidNobody waits for it, so it outlives the requestReturn Task
Fire-and-forget for work that must happenIt's lost if the app stops or the task failsUse a queue and a hosted service
Registering the context as a singleton to "fix" itAll requests share one contextKeep it scoped

Troubleshooting​

SymptomLikely causeFix
The exception shows Object name: 'InventoryDbContext'Something kept the context after its scope endedFind what still holds it
It appears only sometimesA race between the request ending and the taskThe same; it isn't really intermittent
It appears in a singleton serviceThe service kept a contextInject IServiceScopeFactory instead
It appears inside a testThe test disposed of the context firstKeep the context alive for the whole test

FAQ​

Why did the response still say 200?​

The response was already finished. The task runs on its own, and nothing connects its failure to the request.

Should I catch ObjectDisposedException?​

Only to log it, as this example does while finding the problem. It always means a bug.

Conclusion​

"Cannot access a disposed context instance" means code used a DbContext after its scope ended, usually a background task holding on to the request's context. The request answers fine, and the error shows up later or not at all. Create a scope in the task with IServiceScopeFactory, or move the work to a hosted service.

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.