blog post

How To Manage EF Core DbContext Lifetime

A Big Thanks To The Sponsors of This Blog Post

Go From Undiscovered to Growing & Monetizing Your LinkedIn Account. Join 60+ LinkedIn Top Voices and 25,000+ students to accelerate the growth of your personal brand on LinkedIn.

Learn more

Build a better, faster content production system. The Content OS is a multi-step system for creating a high-quality newsletter and 6-12 pieces of high-performance social media content each week.

Learn more

Build a lean, profitable internet business in 2024. The Creator MBA delivers a complete blueprint for starting, building, and sustaining a profitable Internet business.

Learn more

Proper management of the DbContext lifecycle is crucial for application performance and stability.

While in many cases, registering DbContext with a scoped lifetime is simple enough, there are scenarios where more control is needed. EF Core offers more flexibility on DbContext creation.

In this blog post, I will show how to use DbContext, DbContextFactory and their pooled versions. These features allow for greater flexibility and efficiency, especially in applications that require high performance or have specific threading models.

Using DbContext

DbContext is the heart of EF Core, it establishes connection with a database and allows performing CRUD operations.

The DbContext class is responsible for:

  • Managing database connections: opens and closes connections to the database as needed.
  • Change tracking: keeps track of changes made to entities so they can be persisted to the database.
  • Query execution: translates LINQ queries to SQL and executes them against the database.

When working with DbContext, you should be aware of the following nuances:

  • Not thread-safe: should not be shared across multiple threads simultaneously.
  • Lightweight: designed to be instantiated and disposed frequently.
  • Stateful: tracks entity states for change tracking and identity resolution.

Let's explore how you can register DbContext in the DI container:

csharp
builder.Services.AddDbContext<ApplicationDbContext>(options => { options.EnableSensitiveDataLogging().UseNpgsql(connectionString); }); public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); } }

DbContext is registered as Scoped in DI container. It has the lifetime of current scope which equals to the current request duration:

csharp
app.MapPost("/api/authors", async ( [FromBody] CreateAuthorRequest request, ApplicationDbContext context, CancellationToken cancellationToken) => { var author = request.MapToEntity(); context.Authors.Add(author); await context.SaveChangesAsync(cancellationToken); var response = author.MapToResponse(); return Results.Created($"/api/authors/{author.Id}", response); });

You can also manually create a scope and resolve the DbContext:

csharp
using (var scope = app.Services.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); await dbContext.Database.MigrateAsync(); }

DbContext being a Scoped dependency is pretty flexible, you can inject the same instance of DbContext into the Controller/Minimal API endpoint, service, repository. But sometimes, you need more control on when DbContext is created and disposed. Such control you can get with DbContextFactory.

Using DbContextFactory

In some use cases, such as background services, multi-threaded applications, or factories that create services, you might need full control to create and dispose DbContext instance.

The IDbContextFactory<TContext> is a service provided by EF Core that allows creating of DbContext instances on demand. It ensures that each instance is configured correctly and can be used safely without being tied directly to the DI container's service lifetime.

You can register IDbContextFactory<TContext> in the following way, similar to DbContext:

csharp
builder.Services.AddDbContextFactory<ApplicationDbContext>(options => { options.EnableSensitiveDataLogging().UseNpgsql(connectionString); });

IDbContextFactory is registered as Singleton in the DI container.

You can the CreateDbContext or CreateDbContextAsync from the IDbContextFactory to create a DbContext:

csharp
public class HostedService : IHostedService { private readonly IDbContextFactory<ApplicationDbContext> _contextFactory; public HostedService(IDbContextFactory<ApplicationDbContext> contextFactory) { _contextFactory = contextFactory; } public async Task StartAsync(CancellationToken cancellationToken) { await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken); var books = await context.Books.ToListAsync(cancellationToken: cancellationToken); } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; }

DbContext is disposed when the using block ends.

You should use IDbContextFactory<TContext> with caution - make sure that you won't create too many database connections.

You can register both DbContext and IDbContextFactory at the same time. You need to do a small tweak for this and set the optionsLifetime to Singleton as the IDbContextFactory is registered as Singleton:

csharp
builder.Services.AddDbContext<ApplicationDbContext>(options => { options.EnableSensitiveDataLogging().UseNpgsql(connectionString); }, optionsLifetime: ServiceLifetime.Singleton); builder.Services.AddDbContextFactory<ApplicationDbContext>(options => { options.EnableSensitiveDataLogging().UseNpgsql(connectionString); });

Using DbContext Pooling

DbContext pooling is a feature introduced in EF Core that allows for reusing DbContext instances, reducing the overhead of creating and disposing of contexts frequently.

DbContext pooling maintains a pool of pre-configured DbContext instances. When you request a DbContext, it provides one from the pool. When you're done, it resets the state and returns the instance to the pool for reuse.

This feature is crucial for high performance scenarios or when you need to create a lot of database connections.

Registration of Pooled DbContext is straightforward:

csharp
builder.Services.AddDbContextPool<ApplicationDbContext>(options => { options.EnableSensitiveDataLogging().UseNpgsql(connectionString); });

In your classes you inject a regular DbContext without knowing that it is being pooled.

Using DbContextFactory Pooling

You can combine DbContextFactory and DbContext pooling to create a pooled DbContextFactory. This allows you to create DbContext instances on demand, which are also pooled for performance.

csharp
builder.Services.AddPooledDbContextFactory<ApplicationDbContext>(options => { options.EnableSensitiveDataLogging().UseNpgsql(connectionString); });

The API for using pooled factory is the same:

csharp
await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken); var books = await context.Books.ToListAsync(cancellationToken: cancellationToken);

Each CreateDbContextAsync call retrieves a DbContext from the pool. After disposing, the DbContext is returned to the pool.

In your classes you inject a regular DbContextFactory without knowing that it is being pooled.

Summary

Managing the DbContext lifetime is essential for building efficient EF Core applications. By leveraging DbContextFactory and DbContext pooling, you can gain greater control over context creation and optimize performance.

Key Takeaways:

  • DbContextFactory: use when you need to create DbContext instances on demand, especially in multi-threaded or background tasks.
  • DbContext Pooling: use to reduce the overhead of creating and disposing of DbContext instances frequently.
  • Pooled DbContextFactory: combine both features to create pooled DbContext instances on demand.
  • Always Dispose DbContext Instances: use using statements or ensure that contexts are disposed or returned to the pool.
  • Avoid Thread Safety Issues: do not share DbContext instances across threads.

Hope you find this blog post useful. Happy coding!

You can download source code for this blog post for free

Improve Your .NET and Architecture Skills

Join my community of 2300+ developers and architects.

Each week you will get 1 practical tip with best practises and architecture advice.