The Error
InvalidOperationException: Cannot consume scoped service 'AppDbContext' from singleton 'DataService'
Quick Fix - 1 Minute
// BAD: Singleton consuming Scoped - Dev Fix below
// builder.Services.AddSingleton();
// GOOD: Make service Scoped or Transient
builder.Services.AddScoped(); // Dev Fix: Match DbContext lifetime
// OR: Use IServiceScopeFactory if must be Singleton
public class DataService
{
private readonly IServiceScopeFactory _scopeFactory;
public DataService(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;
public async Task Run()
{
using var scope = _scopeFactory.CreateScope(); // Dev Fix
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// use db safely
}
}
Why This Happens
Singleton lives for app lifetime. Scoped lives per request. Singleton holding Scoped = DbContext reused across requests = crashes..NET 8 DI throws at startup to prevent this.
Real-World Scenario: IHostedService Captures DbContext
Most common.NET 8 startup crash. BackgroundService is Singleton by design:
// WRONG: Crashes on startup with InvalidOperationException
public class ReportGenerator : BackgroundService
{
private readonly AppDbContext _db; // Scoped in Singleton = illegal
private readonly ILogger<ReportGenerator> _logger;
public ReportGenerator(AppDbContext db, ILogger<ReportGenerator> logger)
{
_db = db; // DI throws here
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { }
}
// RIGHT: Inject IServiceScopeFactory instead
public class ReportGenerator : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<ReportGenerator> _logger;
public ReportGenerator(IServiceScopeFactory scopeFactory, ILogger<ReportGenerator> logger)
{
_scopeFactory = scopeFactory; // Singleton - safe
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _scopeFactory.CreateScope(); // Dev Fix: New scope each loop
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var data = await db.Reports.ToListAsync(stoppingToken);
// Process...
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
}
}
// Program.cs
builder.Services.AddHostedService<ReportGenerator>(); // Singleton
builder.Services.AddDbContext<AppDbContext>(); // Scoped
Key rule: IHostedService and BackgroundService are always Singleton. They can't inject Scoped services directly. Use IServiceScopeFactory to create a scope per unit of work.
Related Fixes You Should Know
DI lifetime bugs trigger these next:
- DbContext Was Disposed - You fixed the DI error but stored the scoped DbContext in a field. Dispose happens after
usingblock. Always resolve inside the scope. - Service Lifetime Mismatch - Injecting Transient into Singleton captures it forever. Transient becomes Singleton. Use factory or change lifetime.
- BackgroundService CancellationToken - After fixing DI, pass
stoppingTokento EF queries or app hangs on shutdown. - Scoped Service From Root Provider - Same error but in
Program.csafterbuilder.Build(). Useapp.Services.CreateScope()there.
FAQ
Q: Can I just make DbContext Singleton to fix this?
No. DbContext is not thread-safe. Singleton DbContext = multiple requests write to same ChangeTracker = data corruption + memory leaks. Keep it Scoped. Use factory pattern in Singletons.
Q: Why does the error only happen at startup?
.NET 8 validates DI graph on app.Run(). It walks the tree and fails fast if Singleton → Scoped found. This prevents runtime crashes. Older .NET versions crashed on first request instead.
Step-by-Step Debug
- Check lifetimes:
DbContextis Scoped by default - Never inject Scoped into Singleton
- Rule: Singleton → Singleton, Scoped → Scoped/Transient, Transient → Any
- If BackgroundService needs DbContext: use
IServiceScopeFactory
No comments yet. Be the first to share your thoughts!