Dependency Injection in .NET: Service Lifetimes

Using the wrong DI lifetime is the #1 cause of concurrency bugs in ASP.NET Core. We revisit Singleton, Scoped, and Transient with a focus on thread safety.

The Three Lifetimes

LifetimeCreated…Thread Safety
TransientEvery time requestedSafe (Instance per usage)
ScopedOnce per HTTP RequestSafe (Single thread per request)
SingletonOnce per App LifetimeUNSAFE (Shared across all requests)

The Captive Dependency Problem

Injecting a Scoped service into a Singleton service is a fatal error. The Scoped service will stay alive forever (captured), possibly holding onto a DbConnection.

// ❌ BAD
public class SingletonService
{
    private readonly ScopedService _scoped; // Captured!
    public SingletonService(ScopedService scoped) => _scoped = scoped;
}

// ✅ GOOD (IServiceScopeFactory)
public class SingletonService
{
    private readonly IServiceScopeFactory _scopeFactory;
    
    public void DoWork()
    {
        using var scope = _scopeFactory.CreateScope();
        var scoped = scope.ServiceProvider.GetRequiredService<ScopedService>();
        scoped.DoThing();
    }
}

Key Takeaways

  • EF Core `DbContext` is **Scoped**. Never inject it into a Singleton.
  • Use `ValidateScopes = true` in development to catch these bugs early.

Discover more from C4: Container, Code, Cloud & Context

Subscribe to get the latest posts sent to your email.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.