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.

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();
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();
    }
}
Warning

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

Next Steps#

Edit this page on GitHub
Last updated on by Tobias Tengler