You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core配置行版本后并发更新未触发DbUpdateConcurrencyException问题

并发更新未触发DbUpdateConcurrencyException的原因及解决方法

1. PostgreSQL行版本配置的特殊性

EF Core的IsRowVersion()在PostgreSQL提供程序中的行为和SQL Server存在差异:

  • PostgreSQL没有原生的rowversion类型,EF Core默认将标记为IsRowVersion()的属性映射到PostgreSQL的**xmin系统列**(该列存储事务ID,每次行更新都会自动递增)。
  • 若你的Version是手动定义的普通列(而非映射到xmin),IsRowVersion()不会自动让PostgreSQL为其生成版本值,更新时自然不会触发版本检查。

解决方法:

确保配置正确映射到xmin,或手动配置自定义版本列的生成策略:

public void Configure(EntityTypeBuilder<UserItem> builder)
{
    // 映射到PostgreSQL的xmin系统列
    builder.Property(x => x.Version)
        .HasColumnType("xid") // xmin对应xid类型,适配EF的uint
        .IsRowVersion();
}

若使用自定义版本列,需配置自动递增逻辑:

builder.Property(x => x.Version)
    .IsRowVersion()
    .ValueGeneratedOnAddOrUpdate()
    .HasDefaultValueSql("0")
    .HasComputedColumnSql("Version + 1", stored: true);

2. Version属性的类型映射问题

你使用的uint理论上可匹配PostgreSQL的xid类型,但EF Core对无符号整数的映射支持存在边缘场景问题。部分情况下,使用long(有符号整数)会更稳定。

解决方法:

修改Version属性类型为long,配置无需额外调整:

public sealed class UserItem : IEntity
{       
    public long Version { get; set; }
    // 其他属性保持不变
}

3. 查询未追踪Version或使用NoTracking

如果查询时调用了AsNoTracking(),EF Core不会追踪实体的原始Version值,更新时无法生成包含原始版本的WHERE条件,也就不会触发并发异常。另外,若查询使用了排除Version的投影,EF也无法获取原始版本值。

解决方法:

  • 确保使用追踪查询(默认即为追踪,除非显式调用AsNoTracking())。
  • 查询时必须加载Version属性,避免使用排除该属性的投影:
// 正确:加载完整实体,包含Version
var userItem = await _dbContext.UserItems
    .FirstOrDefaultAsync(u => u.UserId == userId && u.ItemId == itemId);

// 错误:投影排除Version,EF无法追踪原始版本
var userItem = await _dbContext.UserItems
    .Where(u => u.UserId == userId && u.ItemId == itemId)
    .Select(u => new { u.Quantity })
    .FirstOrDefaultAsync();

4. 事务隔离级别导致阻塞而非异常

若事务使用可重复读或更高隔离级别,PostgreSQL会对查询行加锁,并发请求会被阻塞至第一个事务提交。此时第二个事务更新的是已提交的最新数据,不会触发版本不匹配异常。

解决方法:

使用默认的读已提交隔离级别,或显式指定:

using var transaction = await _dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
try
{
    // 查询、更新逻辑
    await _dbContext.SaveChangesAsync();
    await transaction.CommitAsync();
}
catch (DbUpdateConcurrencyException)
{
    await transaction.RollbackAsync();
    // 处理并发异常逻辑
}

5. 检查EF Core生成的更新SQL

开启EF Core日志,查看生成的UPDATE语句是否包含Version的WHERE条件。正常的并发检查语句应类似:

UPDATE "UserItems" SET "Quantity" = @p0
WHERE "UserId" = @p1 AND "ItemId" = @p2 AND "Version" = @p3;

若UPDATE语句中无AND "Version" = @p3,说明EF Core未将Version识别为并发令牌,需重新检查配置。

开启日志的方法(示例):

在appsettings.json中添加:

{
  "Logging": {
    "LogLevel": {
      "Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  }
}

内容的提问来源于stack exchange,提问作者Azhari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 06:22:51