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
相关产品推荐
相关产品推荐

