Introduction
ASP.NET Core DI validates service lifetimes to prevent captive dependencies: when a scoped service (like DbContext) is injected into a singleton service, the scoped instance lives for the entire application lifetime instead of being created per-request. Since .NET 6, this throws InvalidOperationException at startup by default.
Symptoms
InvalidOperationException: Cannot consume scoped service 'AppDbContext' from singleton 'CacheService'- Error occurs at application startup during service registration validation
DbContextaccessed from a singleton background service- Works in development with validation disabled but fails in production
- Error after changing a service from scoped to singleton
Example error:
``
System.InvalidOperationException:
Error while validating the service descriptor 'ServiceType: ICacheService
Lifetime: Singleton ImplementationType: CacheService':
Cannot consume scoped service 'AppDbContext' from singleton 'CacheService'.
---> System.InvalidOperationException:
Cannot consume scoped service 'AppDbContext' from singleton 'CacheService'.
Common Causes
- Singleton service needs per-request data access (DbContext)
IHostedService(singleton) injects scoped services- Singleton caching service needs database access
- Background worker needs scoped services
- Misunderstanding of service lifetime relationships
Step-by-Step Fix
- 1.Use IServiceScopeFactory to create scoped instances:
- 2.```csharp
- 3.// WRONG: DbContext injected into singleton
- 4.public class CacheService : ICacheService
- 5.{
- 6.private readonly AppDbContext _context; // Captive dependency!
public CacheService(AppDbContext context) { _context = context; } }
// CORRECT: inject scope factory and create scope when needed public class CacheService : ICacheService { private readonly IServiceScopeFactory _scopeFactory;
public CacheService(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; }
public async Task<User> GetUserAsync(string id) { // Create a new scope for this operation using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<AppDbContext>(); return await context.Users.FindAsync(id); } } ```
- 1.Change the service lifetime to match dependencies:
- 2.```csharp
- 3.// If the service only needs to live per-request, make it scoped
- 4.builder.Services.AddScoped<ICacheService, CacheService>();
// If it needs to be singleton, inject only singleton-scoped dependencies builder.Services.AddSingleton<ICacheService, CacheService>(); // And use IServiceScopeFactory inside CacheService for scoped access ```
- 1.Use factory pattern for scoped service creation:
- 2.```csharp
- 3.public interface IDbContextFactory
- 4.{
- 5.AppDbContext Create();
- 6.}
public class DbContextFactory : IDbContextFactory { private readonly IServiceScopeFactory _scopeFactory; private readonly ConcurrentBag<IDisposable> _scopes = new();
public DbContextFactory(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; }
public AppDbContext Create() { var scope = _scopeFactory.CreateScope(); _scopes.Add(scope); return scope.ServiceProvider.GetRequiredService<AppDbContext>(); } }
// Register as singleton builder.Services.AddSingleton<IDbContextFactory, DbContextFactory>(); ```
- 1.Handle scoped services in BackgroundService:
- 2.```csharp
- 3.public class DataProcessingService : BackgroundService
- 4.{
- 5.private readonly IServiceScopeFactory _scopeFactory;
public DataProcessingService(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // Create a scope for each processing cycle using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var pendingItems = await context.PendingItems.ToListAsync(stoppingToken); foreach (var item in pendingItems) { await ProcessItemAsync(item, context, stoppingToken); }
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); } } } ```
Prevention
- Keep
ValidateScopes = truein all environments (it is the default) - Inject
IServiceScopeFactoryinto singletons that need scoped services - Never inject scoped services directly into singletons
- Use
IHttpContextAccessorfor per-request data in singletons - Document service lifetime requirements in interface comments
- Use analyzers to detect lifetime mismatches at compile time