Entity Framework Core

Learn how to integrate Entity Framework Core with Hot Chocolate v16, including DbContext injection and factory patterns.

Entity Framework Core is a powerful object-relational mapping framework that has become a staple when working with SQL-based databases in .NET applications.

Resolver Injection of a DbContext

When using the default scope for queries, each resolver that accepts a scoped DbContext receives a separate instance. This avoids threading issues.

C#
public static async Task<Book?> GetBookByIdAsync(
ApplicationDbContext dbContext) => // ...

When using the default scope for mutations, each mutation resolver that accepts a scoped DbContext receives the same request-scoped instance, as mutations execute sequentially.

C#
public static async Task<Book> AddBookAsync(
AddBookInput input,
AppDbContext dbContext) => // ...

See the Dependency Injection documentation for more details.

Warning: Changing the default scope for queries will likely result in the error "A second operation started on this context before a previous operation completed", because Entity Framework Core does not support multiple parallel operations on the same DbContext instance.

Using a DbContext Factory

To use a DbContext factory, register your DbContext with Hot Chocolate. Install the additional package:

Bash
dotnet add package HotChocolate.Data.EntityFramework
Warning
All HotChocolate.* packages need to have the same version.

Call the RegisterDbContextFactory<T> method on the IRequestExecutorBuilder. The Hot Chocolate resolver compiler then takes care of injecting your DbContext instance into resolvers.

C#
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddDbContextFactory<ApplicationDbContext>(
options => options.UseSqlServer("YOUR_CONNECTION_STRING"));
// ... or AddPooledDbContextFactory.
builder
.AddGraphQL()
.RegisterDbContextFactory<ApplicationDbContext>()
.AddTypes();
C#
[QueryType]
public static class Query
{
public static async Task<Book?> GetBookByIdAsync(
Guid id,
ApplicationDbContext dbContext)
{
return await dbContext.Books.FindAsync(id);
}
}

Warning: You still need to add your DbContextFactory to the dependency injection container by calling AddDbContextFactory<T> or AddPooledDbContextFactory<T>. RegisterDbContextFactory<T> on its own is not enough.

Working with a DbContext Factory

When you use a DbContext factory, you need to access the DbContext differently outside of direct resolver injection.

DataLoaders

When creating DataLoaders that need access to your DbContext, inject the IDbContextFactory<T> through the constructor. Create and dispose the DbContext within the LoadBatchAsync method.

C#
public sealed class BookByIdDataLoader : BatchDataLoader<Guid, Book>
{
private readonly IDbContextFactory<AppDbContext>
_dbContextFactory;
public BookByIdDataLoader(
IDbContextFactory<AppDbContext> dbContextFactory,
IBatchScheduler batchScheduler,
DataLoaderOptions options)
: base(batchScheduler, options)
{
_dbContextFactory = dbContextFactory;
}
protected override async Task<IReadOnlyDictionary<Guid, Book>>
LoadBatchAsync(
IReadOnlyList<Guid> keys,
CancellationToken cancellationToken)
{
using AppDbContext dbContext =
_dbContextFactory.CreateDbContext();
return await dbContext.Books
.Where(b => keys.Contains(b.Id))
.ToDictionaryAsync(b => b.Id, cancellationToken);
}
}

Warning: Dispose the DbContext after use. The example above uses the using statement for this purpose.

Services

Services that need a DbContext should inject IDbContextFactory<T> instead of the DbContext directly.

C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContextFactory<ApplicationDbContext>(
options => options.UseSqlServer("YOUR_CONNECTION_STRING"));
builder.Services.AddScoped<BookService>();
builder
.AddGraphQL()
.AddTypes();
C#
public sealed class BookService : IAsyncDisposable
{
private readonly ApplicationDbContext _dbContext;
public BookService(
IDbContextFactory<ApplicationDbContext> dbContextFactory)
{
_dbContext = dbContextFactory.CreateDbContext();
}
public async Task<Book?> GetBookAsync(Guid id)
{
return await _dbContext.Books.FindAsync(id);
}
public ValueTask DisposeAsync()
{
return _dbContext.DisposeAsync();
}
}
C#
[QueryType]
public static class Query
{
public static async Task<Book?> GetBookByIdAsync(
Guid id,
BookService bookService)
{
return await bookService.GetBookAsync(id);
}
}

Warning: Dispose the DbContext when the service is disposed. The example above implements IAsyncDisposable and disposes the DbContext in DisposeAsync.

Next Steps

Last updated on April 13, 2026 by Michael Staib