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

发生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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 10:04:30