返回

EF Core 保存数据时报错 DbUpdateConcurrencyException 怎么处理?

2026-10-20 EF Core 报错 2 0

在使用 EF Core 开发 ASP.NET Core 应用时,调用 SaveChanges() 或 SaveChangesAsync() 保存数据,偶尔会遇到 DbUpdateConcurrencyException 异常。常见场景包括修改用户信息、更新订单状态、删除数据库记录,以及多个请求同时操作同一条数据。

这个异常通常与并发冲突有关,但不一定意味着程序真的出现了多个请求同时修改数据的情况。要解决问题,需要先判断 EF Core 为什么认为数据库中的实际操作结果与预期不一致。

DbUpdateConcurrencyException 是什么原因?

EF Core 默认会跟踪实体的原始值和当前值。如果实体配置了并发令牌,EF Core 在更新或删除记录时,会将原始并发令牌值放入 SQL 的 WHERE 条件中。例如,实体配置了 RowVersion 字段。当程序读取数据后,另一个请求先修改了这条记录,数据库中的版本号发生变化。此时第一个请求再保存数据,更新语句可能匹配不到记录,EF Core 就会抛出 DbUpdateConcurrencyException。

常见原因包括:

  • 数据已被其他请求修改:当前请求读取的数据已经过期。
  • 记录已被删除:程序准备更新的记录已不存在。
  • 并发令牌不匹配:例如 SQL Server 的 rowversion 字段与原始值不同。
  • 实体状态或主键设置错误:程序错误地将实体标记为待更新或待删除,导致 SQL 未匹配到预期记录。

需要注意,普通的数据库字段不会自动启用乐观并发检查。是否检查并发令牌,取决于实体配置和实际操作方式。

如何排查和解决 DbUpdateConcurrencyException?

检查数据库记录是否存在

如果异常发生在更新或删除操作中,可以先根据主键重新查询数据库,确认记录是否仍然存在。

var entity = await dbContext.Users
    .FirstOrDefaultAsync(x => x.Id == userId);

if (entity == null)
{
    // 记录不存在,结束当前操作
    return;
}

entity.Name = "新名称";
await dbContext.SaveChangesAsync();

如果记录存在但仍然报错,需要进一步检查并发令牌、实体原始值和更新条件。

捕获异常并处理并发冲突

对于允许用户重新确认修改内容的业务,可以捕获异常并获取数据库当前值。

try
{
    await dbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
    foreach (var entry in ex.Entries)
    {
        var databaseValues =
            await entry.GetDatabaseValuesAsync();

        if (databaseValues == null)
        {
            // 数据已被删除
            // 通知用户或结束当前操作
            continue;
        }

        // 数据仍然存在,提示用户刷新后重新编辑
        entry.State = EntityState.Detached;
    }

    throw;
}

这段代码用于说明如何识别冲突,并未自动完成数据合并。实际项目中,如果需要保留用户提交的修改,应先保存待处理的值,再根据业务规则决定采用数据库值、用户值或合并后的值。示例最后重新抛出异常,调用方需要负责反馈或记录错误。

SQL Server 使用 RowVersion 检测并发

如果业务需要防止用户修改旧数据时覆盖其他人的更新,可以给实体增加 RowVersion 字段。

public class User
{
    public int Id { get; set; }

    public string Name { get; set; } = "";

    [Timestamp]
    public byte[] RowVersion { get; set; } = [];
}

需要引入 System.ComponentModel.DataAnnotations。在 SQL Server 中,[Timestamp] 会将该属性配置为数据库生成的并发令牌,对应数据库的 rowversion 类型。

数据库每次更新记录时都会生成新的版本值。EF Core 保存数据时会检查原始版本是否仍然匹配,从而发现并发冲突。其他数据库的配置方式可能不同,不能直接照搬 SQL Server 的实现。

还有哪些问题需要检查?

如果确认没有真实的并发操作,建议继续排查以下情况:

  • 检查 DbContext 是否被多个线程或请求同时使用。EF Core 不支持同一个 DbContext 实例上的并行操作。
  • 检查更新前是否使用了 Attach()、Update() 或手动修改实体状态,避免实体状态与实际数据库记录不一致。
  • 开启 EF Core SQL 日志,查看生成的 UPDATE 或 DELETE 语句及其 WHERE 条件,确认主键和并发令牌是否正确。
  • 区分 DbUpdateConcurrencyException 与 DbUpdateException。唯一索引冲突、外键约束失败等问题通常属于后者,不应使用同一种方式处理。

总结

EF Core 保存数据时出现 DbUpdateConcurrencyException,应先确认记录是否存在,再检查并发令牌和 SQL 更新条件。对于多人编辑业务,可以采用 RowVersion 实现乐观并发控制,并在冲突发生时提示用户刷新、重新编辑或合并数据。不要简单地循环重试或直接覆盖数据库值,否则可能造成数据丢失。

顶部