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.