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

EF Core时态表报错0x80131904,如何正确实现SaveChangesAsync重试?

EF Core 时态表高并发错误的重试方案解决

一、先纠正错误码的核心问题

你配置里用的80131904是.NET封装后的HRESULT,并非SQL Server原生错误码,EF Core的EnableRetryOnFailure不会识别这个值。实际触发该提示的SQL Server原生错误码是13535,这个数值在int范围内,可以直接用于重试配置。

二、关于错误属性的确认

这个错误完全属于瞬态错误:仅在高并发修改同一条时态表记录时偶发,不存在数据损坏问题,重试操作时事务时间戳更新,即可避开时间重叠冲突,成功执行。

三、正确的EF Core内置重试配置

将错误码替换为13535,调整后的配置示例:

options.UseSqlServer(connectionString,
    sqlOptions =>
    {
        sqlOptions.MigrationsAssembly(MigrationAssemblyName);
        sqlOptions.EnableRetryOnFailure(
            maxRetryCount: 10,
            maxRetryDelay: TimeSpan.FromSeconds(30),
            errorNumbersToAdd: new List<int> { 13535 });
        sqlOptions.CommandTimeout(240);
    });

四、更灵活的重试方案

1. 自定义重试策略

如果需要更精细化的重试逻辑(比如指数退避、区分错误类型调整间隔),可以自定义SqlServerRetryingExecutionStrategy实现:

public class TemporalTableRetryStrategy : SqlServerRetryingExecutionStrategy
{
    private static readonly ICollection<int> _targetErrors = new List<int> { 13535 };

    public TemporalTableRetryStrategy(
        DbContext context,
        int maxRetryCount = 10,
        TimeSpan maxRetryDelay = default,
        ICollection<int>? extraErrors = null)
        : base(context, maxRetryCount, maxRetryDelay ?? TimeSpan.FromSeconds(30), CombineErrors(extraErrors))
    {
    }

    private static ICollection<int> CombineErrors(ICollection<int>? extraErrors)
    {
        var combined = new List<int>(_targetErrors);
        if (extraErrors != null) combined.AddRange(extraErrors);
        return combined;
    }

    protected override bool ShouldRetryOn(Exception exception)
    {
        // 优先判断时态表专属错误,再保留EF默认重试逻辑
        if (exception is SqlException sqlEx)
        {
            return sqlEx.Errors.Cast<SqlError>().Any(e => _targetErrors.Contains(e.Number));
        }
        return base.ShouldRetryOn(exception);
    }
}

之后在DbContext中替换默认执行策略:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer(connectionString)
                  .ReplaceService<IExecutionStrategy, TemporalTableRetryStrategy>();
}

2. 业务层手动重试

针对特定操作(比如批量更新)需要单独控制重试逻辑时,可以在业务层封装重试:

public async Task<int> SaveWithTemporalRetryAsync(CancellationToken cancellationToken = default)
{
    int retryCount = 0;
    const int maxRetries = 10;
    while (true)
    {
        try
        {
            return await SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateException ex) 
            when (ex.InnerException is SqlException sqlEx && 
                  sqlEx.Errors.Cast<SqlError>().Any(e => e.Number == 13535))
        {
            retryCount++;
            if (retryCount >= maxRetries) throw;
            // 指数退避等待,避免高频重试加剧冲突
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)), cancellationToken);
        }
    }
}

注意事项

  • 重试操作必须保证幂等性:比如不要在重试逻辑中重复生成新实体状态,避免重复插入数据。
  • 重试间隔建议采用指数退避,初始间隔不宜过短,否则会加剧并发冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 18:05:57