ASP.NET Core DI lifetimes and captive dependencies: scopes in a C# BackgroundService

ASP.NET Core Interview Q&A November 13, 2026 csharp

Transient: a new instance every time.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ======================================================================
// Q: Explain DI lifetimes. What's a captive dependency?
//
// - Transient: a new instance every time.
// - Scoped: one instance per request. This is how DbContext is registered.
// - Singleton: one instance for the whole application.
// A captive dependency is when a singleton holds a scoped service. A DbContext
// then gets shared across threads, even though it is not thread-safe. In
// background services you create your own scope.
// ======================================================================

// BackgroundService that creates its own scope per run
public class PriceSyncWorker(IServiceScopeFactory scopes, ILogger<PriceSyncWorker> log)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(15));
        while (await timer.WaitForNextTickAsync(ct))
        {
            await using var scope = scopes.CreateAsyncScope();
            var sync = scope.ServiceProvider.GetRequiredService<IPriceSyncService>();
            try { await sync.RunAsync(ct); }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                log.LogError(ex, "Price sync failed"); // the worker must not die
            }
        }
    }
}