Entity Framework Core
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.
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:
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();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.
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);
}
}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.
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();
}
}Dispose the DbContext when the service is disposed. The example above implements IAsyncDisposable and disposes the DbContext in DisposeAsync.
Next Steps#
- Dependency Injection for DI scope configuration
- DataLoader for batching patterns
- Filtering for applying filters to EF Core queries