Chapter 2 · Practice 1 of 4
Never inject a scoped service into a singleton
Why it matters
Every registration has a lifetime:
| Lifetime | Created | Typical use |
|---|---|---|
| Transient | Every time it is requested | Lightweight, stateless helpers |
| Scoped | Once per request | DbContext, unit of work, per-request state |
| Singleton | Once per app | Caches, clients, configuration |
The docs warn: do not resolve a scoped service from a singleton. The scoped service then lives as long as the singleton, a captive dependency, so one request's DbContext and data leak into later requests.
By default, in the Development environment, the container throws when a service resolves one with a shorter lifetime. Do not turn that validation off. When a singleton or a background service needs scoped services, create a scope with IServiceScopeFactory.
Singletons must also be thread safe, since every request shares them.
Example
public sealed class NightlyReport(IServiceScopeFactory scopes) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
await using var scope = scopes.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<ShopDbContext>();
await db.Orders.Where(o => o.PlacedAt < DateTime.UtcNow.AddDays(-30))
.ExecuteDeleteAsync(ct);
}
}