website/src/docs/hotchocolate/v16/integrations/entity-framework.md
Entity Framework Core is a powerful object-relational mapping framework that has become a staple when working with SQL-based databases in .NET applications.
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.
To use a DbContext factory, register your DbContext with Hot Chocolate. Install the additional package:
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);
}
}
public sealed class QueryType : ObjectType
{
protected override void Configure(
IObjectTypeDescriptor descriptor)
{
descriptor.Name(OperationTypeNames.Query);
descriptor
.Field("bookById")
.Argument("id", a => a.Type<NonNullType<UuidType>>())
.Resolve(async ctx => await ctx
.Service<IDbContextFactory<ApplicationDbContext>>()
.CreateDbContext()
.Books
.FindAsync(ctx.ArgumentValue<Guid>("id")));
}
}
Take a look at the annotation-based or code-first example.
</Schema> </ExampleTabs>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.
When you use a DbContext factory, you need to access the DbContext differently outside of direct resolver injection.
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 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);
}
}
public sealed class QueryType : ObjectType
{
protected override void Configure(
IObjectTypeDescriptor descriptor)
{
descriptor.Name(OperationTypeNames.Query);
descriptor
.Field("bookById")
.Argument("id", a => a.Type<NonNullType<UuidType>>())
.Resolve<Book?>(async ctx => await ctx
.Service<BookService>()
.GetBookAsync(ctx.ArgumentValue<Guid>("id")));
}
}
Take a look at the annotation-based or code-first example.
</Schema> </ExampleTabs>Warning: Dispose the
DbContextwhen the service is disposed. The example above implementsIAsyncDisposableand disposes theDbContextinDisposeAsync.