Fixed: "Cannot consume scoped service 'DbContextOptions' from singleton" in ASP.NET Core
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:
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();
}
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:
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'.

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:
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.
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"
]
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.

Common Mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
Registering the DbContext as a singleton | The error goes, and every request shares one context | Keep it scoped |
Turning off ValidateScopes | The check goes, and the bug stays | Use IServiceScopeFactory |
Resolving it from IServiceProvider in the constructor | The same captive context, with no check | Create a scope when you need it |
| Keeping the scope in a field | The context lives forever again | Dispose of the scope in the method |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Cannot consume scoped service … from singleton … at startup | A singleton depends on something scoped | Use IServiceScopeFactory, or change a lifetime |
| The same message in a hosted service | Hosted services are singletons | Create a scope in ExecuteAsync |
| No error in Production, and strange bugs | The check runs in Development by default | Test in Development |
Cannot resolve scoped service … from root provider | The same kind of mistake at startup | See 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
- Dependency injection guidelines — captive dependencies and scope validation.
- DbContext Lifetime, Configuration, and Initialization — why contexts are short-lived.
