CodeOath
← All posts
.NET Core / Web API70 min total · 19 parts

Building REST APIs with ASP.NET Core: Routing, Middleware, and Dependency Injection

Contents — Part 11 of 19: Entity Framework Core: DbContext Lifetime and Async Queries
Part 11 of 19 · ~1 min

Entity Framework Core: DbContext Lifetime and Async Queries

DbContext is registered Scoped by design, and this ties directly back to the DI section above — one DbContext instance (and the change-tracking it does internally) per HTTP request:

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
public class OrderService : IOrderService
{
    private readonly AppDbContext _db;
    public OrderService(AppDbContext db) => _db = db;

    public async Task<Order?> GetByIdAsync(int id) =>
        await _db.Orders.FirstOrDefaultAsync(o => o.Id == id); // async — doesn't block a thread on I/O

    public async Task<Order> CreateAsync(CreateOrderRequest request)
    {
        var order = new Order { CustomerName = request.CustomerName, Quantity = request.Quantity };
        _db.Orders.Add(order);
        await _db.SaveChangesAsync(); // one round trip, applying every tracked change in this DbContext
        return order;
    }
}

DbContext is explicitly documented as not thread-safe — a single instance must not be used by two operations concurrently, which is exactly why registering it as anything other than Scoped (a Singleton, or sharing one across background threads) is a common and serious bug, not just a style concern. Always prefer the async EF Core methods (ToListAsync, FirstOrDefaultAsync, SaveChangesAsync) over their synchronous counterparts in a web API — a synchronous call blocks a thread pool thread for the entire duration of the database round trip, and thread pool threads are a limited, shared resource across every concurrent request the server is handling.