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

