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.
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.
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
DbContextinstance.
Using a DbContext Factory
To use a DbContext factory, register your DbContext with Hot Chocolate. Install the additional package:
dotnet add package HotChocolate.Data.EntityFrameworkHotChocolate.* 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.
var builder = WebApplication.CreateBuilder(args);
builder.Services .AddDbContextFactory<ApplicationDbContext>( options => options.UseSqlServer("YOUR_CONNECTION_STRING"));
// ... or AddPooledDbContextFactory.
builder .AddGraphQL() .RegisterDbContextFactory<ApplicationDbContext>() .AddTypes();
[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
DbContextFactoryto the dependency injection container by callingAddDbContextFactory<T>orAddPooledDbContextFactory<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.
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
DbContextafter use. The example above uses theusingstatement for this purpose.
Services
Services that need a DbContext should inject IDbContextFactory<T> instead of the DbContext directly.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContextFactory<ApplicationDbContext>( options => options.UseSqlServer("YOUR_CONNECTION_STRING"));
builder.Services.AddScoped<BookService>();
builder .AddGraphQL() .AddTypes();
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(); }}
[QueryType]public static class Query{ public static async Task<Book?> GetBookByIdAsync( Guid id, BookService bookService) { return await bookService.GetBookAsync(id); }}
Warning: Dispose the
DbContextwhen the service is disposed. The example above implementsIAsyncDisposableand disposes theDbContextinDisposeAsync.
Next Steps
- Dependency Injection for DI scope configuration
- DataLoader for batching patterns
- Filtering for applying filters to EF Core queries