发生DbUpdateConcurrencyException时,如何获取数据库中的最新值?
发生DbUpdateConcurrencyException时,如何获取数据库中的最新值?
我之前处理EF Core并发冲突时也碰到过类似的问题,你遇到的GetDatabaseValuesAsync()返回旧值的情况,大概率是因为DbContext的一级缓存干扰,或者你的并发令牌配置有问题。咱们一步步来解决:
先排查核心问题:为什么GetDatabaseValuesAsync()返回旧值?
EF Core的GetDatabaseValuesAsync()理论上会直接去数据库查询实体的最新值,但如果你的DbContext实例中已经跟踪了该实体的旧版本,有时候会出现缓存没有及时更新的情况;另外,如果你没有正确配置RowVersion字段的数据库生成规则,也可能导致并发判断逻辑混乱,让你误以为拿到的是旧值。
修正后的解决方案
我给你调整了代码,同时加上关键的注释说明:
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { const int retryCount = 3; for (int i = 0; i < retryCount; i++) { try { var result = await base.SaveChangesAsync(cancellationToken); return result; } catch (DbUpdateConcurrencyException ex) { if (i == retryCount - 1) { throw; } foreach (var entry in ex.Entries) { if (entry.Entity is Person) { // 关键步骤1:从数据库强制获取最新值,绕过上下文缓存 var databaseValues = await entry.GetDatabaseValuesAsync(); if (databaseValues == null) { throw new Exception("Person entity was deleted before update."); } // 提取各版本的值 var proposedValues = entry.CurrentValues; var originalValues = entry.OriginalValues; // 合并逻辑:保留用户修改的属性,未修改的用数据库最新值 foreach (var property in proposedValues.Properties) { // 注意:值比较要考虑值类型和引用类型的差异,复杂类型需要自定义比较逻辑 var proposedValue = proposedValues[property]; var databaseValue = databaseValues[property]; var originalValue = originalValues[property]; if (!Equals(originalValue, proposedValue)) { // 用户修改过该属性,保留用户输入 proposedValues[property] = proposedValue; } else { // 用户没修改,用数据库最新值 proposedValues[property] = databaseValue; } } // 关键步骤2:更新原始值为数据库最新值,确保下次SaveChanges用这个值做并发校验 entry.OriginalValues.SetValues(databaseValues); // 额外:如果RowVersion是数据库自动生成的,单独确认最新值已被跟踪 entry.Property(nameof(Person.RowVersion)).OriginalValue = databaseValues[nameof(Person.RowVersion)]; } else { throw new NotSupportedException( $"Don't know how to handle concurrency conflicts for entity {entry.Metadata.Name}"); } } // 重试前短暂延迟,避免立即触发新的冲突 await Task.Delay(50 * (i + 1), cancellationToken); } } return 0; }
额外的关键配置:确保RowVersion字段正确工作
你的Person类中RowVersion用了[ConcurrencyCheck],但如果没有配置数据库自动生成,每次更新后这个字段不会自动变化,会导致后续一直触发并发冲突。建议你修改RowVersion的配置:
public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } // 改用Timestamp特性,EF Core会自动配置为数据库生成的并发令牌(对应SQL Server的rowversion类型) [Timestamp] public byte[] RowVersion { get; set; } }
如果坚持用long类型,需要手动配置数据库生成规则:
[ConcurrencyCheck] [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public long RowVersion { get; set; }
同时要在数据库中给RowVersion字段设置默认值或触发器,确保每次更新实体时自动递增(比如SQL Server可以用序列值或触发器实现)。
另一种兜底方案:绕开上下文缓存查询最新值
如果上面的方法还是不行,你可以直接从数据库重新查询实体,完全绕开DbContext的缓存:
// 在处理冲突条目时,替换GetDatabaseValuesAsync的逻辑 var personId = (int)entry.Property(nameof(Person.Id)).CurrentValue; var latestPerson = await _context.Persons.AsNoTracking() .FirstOrDefaultAsync(p => p.Id == personId, cancellationToken); if (latestPerson == null) { throw new Exception("Person entity was deleted before update."); } // 把latestPerson的属性作为数据库最新值来使用 var databaseValues = _context.Entry(latestPerson).CurrentValues;
这样就能确保拿到的是数据库里的最新数据了。
备注:内容来源于stack exchange,提问作者CortoMaltese
相关产品推荐
相关产品推荐

