DbContext 应该被当成一次性、串行使用的工作单元,而不是可共享的并发容器。只要一段业务里存在并行执行,就要拆成多个 DbContext 实例,不要试图让同一个实例同时跑多个 EF Core 操作。

原文链接

我的结论

关键信息

1. DbContext 的设计前提是单次工作单元

一次典型的 EF Core 工作流通常是:

  1. 创建 DbContext
  2. 查询或附加实体并开始跟踪
  3. 修改实体
  4. 调用 SaveChanges / SaveChangesAsync
  5. 释放 DbContext

这意味着它不是长期缓存,也不是跨多个并发任务共享的状态容器。

2. Web 请求与 scoped 生命周期天然契合

AddDbContext 默认会把 DbContext 注册成 scoped,因此在常见 Web 应用里,一个请求对应一个 DbContext 实例 是比较自然的模型:

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));

如果控制器和服务都只在当前请求内串行访问数据库,这种模式既简单又安全。

3. EF Core 明确不支持同一个 DbContext 上的并行操作

下面这种写法是高风险的:

// ❌ 同一个 DbContext 上同时发起两个 EF Core 操作
Task<List<User>> usersTask = context.Users
    .Where(u => u.IsActive)
    .ToListAsync();

Task<List<Order>> ordersTask = context.Orders
    .Where(o => o.Status == OrderStatus.Pending)
    .ToListAsync();

await Task.WhenAll(usersTask, ordersTask);

问题不在 Task.WhenAll,而在于两个任务共享了同一个 context。EF Core 官方结论很直接:同一个 DbContext 实例不支持多条并行操作。

4. 正确做法只有两类

串行执行:立即 await

List<User> users = await context.Users
    .Where(u => u.IsActive)
    .ToListAsync();

List<Order> orders = await context.Orders
    .Where(o => o.Status == OrderStatus.Pending)
    .ToListAsync();

并行执行:每个并行分支单独创建 DbContext

await using ApplicationDbContext usersContext =
    await factory.CreateDbContextAsync();
await using ApplicationDbContext ordersContext =
    await factory.CreateDbContextAsync();

Task<List<User>> usersTask = usersContext.Users
    .Where(u => u.IsActive)
    .ToListAsync();

Task<List<Order>> ordersTask = ordersContext.Orders
    .Where(o => o.Status == OrderStatus.Pending)
    .ToListAsync();

await Task.WhenAll(usersTask, ordersTask);

如果确实要并发访问数据库,核心原则不是“怎么让一个 DbContext 线程安全”,而是“不要共享它”。

摘录

A DbContext instance is designed to be used for a single unit-of-work. This means that the lifetime of a DbContext instance is usually very short.

In many web applications, each HTTP request corresponds to a single unit-of-work.

Entity Framework Core does not support multiple parallel operations being run on the same DbContext instance.

Therefore, always await async calls immediately, or use separate DbContext instances for operations that execute in parallel.

我的补充

ref