Skip to main content

Fixed: "Cannot consume scoped service 'DbContextOptions' from singleton" in ASP.NET Core

· 6 min read
Jagdish Kumawat
Founder @ Dewiride

My API refused to start: Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions1[…InventoryDbContext]' from singleton 'Dewiride.Inventory.Api.CategoryCache'.A small cache class, registered as a singleton, asked for theDbContext. A singleton lives as long as the app, and a DbContextbelongs to one request, so ASP.NET Core stops it.IServiceScopeFactory` is the way for a singleton to use one.

What I Was Doing​

In a .NET 10 Web API with EF Core 10, I wanted to keep the list of category names in memory, so I wrote a small cache and registered it as a singleton, since there should only be one:

src/Dewiride.Inventory.Api/CategoryCache.cs
using Dewiride.Inventory.Data;
using Microsoft.EntityFrameworkCore;

namespace Dewiride.Inventory.Api;

public sealed class CategoryCache(InventoryDbContext db)
{
private List<string>? _names;

public async Task<List<string>> NamesAsync() =>
_names ??= await db.Categories.OrderBy(c => c.Name).Select(c => c.Name).ToListAsync();
}
src/Dewiride.Inventory.Api/Program.cs (excerpt)
builder.Services.AddSingleton<CategoryCache>();

What I Expected​

The API starts, and the first request fills the cache.

What I Got​

dotnet run built the project, and then the app stopped straight away:

Output (the long lines are cut, the stack trace removed)
Unhandled exception. System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Dewiride.Inventory.Api.CategoryCache Lifetime: Singleton ImplementationType: Dewiride.Inventory.Api.CategoryCache': Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions`1[Dewiride.Inventory.Data.InventoryDbContext]' from singleton 'Dewiride.Inventory.Api.CategoryCache'.)
---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Dewiride.Inventory.Api.CategoryCache Lifetime: Singleton ImplementationType: Dewiride.Inventory.Api.CategoryCache': Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions`1[Dewiride.Inventory.Data.InventoryDbContext]' from singleton 'Dewiride.Inventory.Api.CategoryCache'.
---> System.InvalidOperationException: Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions`1[Dewiride.Inventory.Data.InventoryDbContext]' from singleton 'Dewiride.Inventory.Api.CategoryCache'.

A terminal where dotnet run fails at startup with System.AggregateException: Some services are not able to be constructed, and Cannot consume scoped service DbContextOptions from singleton CategoryCache.

What I Checked​

Why it mentions DbContextOptions, not the context. The cache asks for InventoryDbContext, and the context asks for its options, which AddDbContext registers as scoped. The check found that one first, on the way down.

What the guidelines call this. .NET's dependency injection guidelines call it a "captive dependency": "a longer-lived service holds a shorter-lived service captive."

Why this is more than a rule. EF Core's DbContext lifetime page says "DbContext is Not thread-safe". A singleton is shared by every request at once, so one context inside it would be used by all of them, for the app's whole life, with its change tracker growing all the time.

Why It Happens​

Each registered service has a lifetime, and ASP.NET Core checks when it starts, in Development, that no singleton depends on something scoped. If it didn't, the first scoped instance would be kept forever, with bugs that only show up under load.

The Fix​

Ask for IServiceScopeFactory, which is a singleton itself, and create a scope only for the moment you need the context:

src/Dewiride.Inventory.Api/CategoryCache.cs
using Dewiride.Inventory.Data;
using Microsoft.EntityFrameworkCore;

namespace Dewiride.Inventory.Api;

public sealed class CategoryCache(IServiceScopeFactory scopes)
{
private List<string>? _names;

public async Task<List<string>> NamesAsync()
{
if (_names is null)
{
await using var scope = scopes.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<InventoryDbContext>();
_names = await db.Categories.OrderBy(c => c.Name).Select(c => c.Name).ToListAsync();
}

return _names;
}
}

await using disposes of the scope and its context when the method ends. If the class doesn't need to be a singleton, the simpler fix is AddScoped<CategoryCache>(), though then it isn't a cache across requests.

After the Fix​

The API starts, and two requests in a row show the cache working: only the first one reaches MySQL.

Response (GET /demo/cache)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[
"Adapters",
"Cables",
"Docks",
"Keyboards and mice",
"Monitors",
"Printers",
"Stands and mounts",
"Wall chargers"
]
API log (the first request)
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (15ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT `c`.`Name`
FROM `categories` AS `c`
ORDER BY `c`.`Name`

The second request gives the same answer, and its log is empty.

A terminal with a GET request to the cache endpoint answering 200 OK with the category names, and the SELECT that the first request sent to MySQL.

Common Mistakes​

MistakeWhat happensDo this instead
Registering the DbContext as a singletonThe error goes, and every request shares one contextKeep it scoped
Turning off ValidateScopesThe check goes, and the bug staysUse IServiceScopeFactory
Resolving it from IServiceProvider in the constructorThe same captive context, with no checkCreate a scope when you need it
Keeping the scope in a fieldThe context lives forever againDispose of the scope in the method

Troubleshooting​

SymptomLikely causeFix
Cannot consume scoped service … from singleton … at startupA singleton depends on something scopedUse IServiceScopeFactory, or change a lifetime
The same message in a hosted serviceHosted services are singletonsCreate a scope in ExecuteAsync
No error in Production, and strange bugsThe check runs in Development by defaultTest in Development
Cannot resolve scoped service … from root providerThe same kind of mistake at startupSee that post

FAQ​

Why does it only happen in Development?​

ValidateScopes and ValidateOnBuild are on by default in Development, so mistakes show up early. In Production, the app would start and carry the bug.

Is this cache safe?​

It's a small example, and two first requests at once could both run the query. For a real cache, use IMemoryCache or HybridCache.

Conclusion​

"Cannot consume scoped service … from singleton" means a long-lived class asked for something that belongs to a single request, here a DbContext. Inject IServiceScopeFactory, and create a scope around each use. Don't make the context a singleton, because a DbContext isn't safe to share.

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.