如何在EF Core 3.1中集成Polly实现数据库操作的弹性容错策略
实现方案
前提依赖
首先安装所需NuGet包:
PollyMicrosoft.EntityFrameworkCoreMicrosoft.Data.SqlClient
步骤1:定义符合要求的Polly容错策略
首先封装SQL瞬时错误判断逻辑,以及组合「重试+熔断+降级」的策略链:
using Polly; using Polly.CircuitBreaker; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; public static class SqlResiliencePolicy { // Azure SQL 已知瞬时错误编号 private static readonly int[] TransientErrorNumbers = { 4060,40197,40501,40613,49918,49919,49920,11001,10929,10928, 10060,10054,10053,1205,233,121,64,20,-2 /*查询超时*/ }; public static IAsyncPolicy GetCombinedPolicy() { // 1. 指数退避重试策略:最多6次,间隔为 2^n 秒 var retryPolicy = Policy .Handle<Exception>(ex => IsTransientError(ex)) .WaitAndRetryAsync( retryCount: 6, sleepDurationProvider: (retryAttempt, _) => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetryAsync: (_, _, _, _) => Task.CompletedTask // 可在此处添加重试日志逻辑 ); // 2. 熔断策略:连续6次失败后断路25秒 var circuitBreakerPolicy = Policy .Handle<Exception>(ex => IsTransientError(ex)) .CircuitBreakerAsync( exceptionsAllowedBeforeBreaking: 6, durationOfBreak: TimeSpan.FromSeconds(25) ); // 3. 降级策略:熔断/重试耗尽后执行 var fallbackPolicy = Policy .Handle<Exception>(ex => IsTransientError(ex) || ex is BrokenCircuitException) .FallbackAsync( fallbackAction: async (context, ct) => { // 此处写业务降级逻辑,示例如下: // 写操作直接抛业务异常提示用户稍后重试 if (context.TryGetValue("IsWriteOperation", out var isWrite) && (bool)isWrite) { throw new InvalidOperationException("数据库临时不可用,请稍后重试"); } // 读操作可返回空结果/缓存值 await Task.CompletedTask; return default; } ); // 组合策略执行顺序:降级 -> 熔断 -> 重试 return fallbackPolicy.WrapAsync(circuitBreakerPolicy).WrapAsync(retryPolicy); } // 错误类型判断逻辑 private static bool IsTransientError(Exception ex) { if (ex is SqlException sqlEx) { return sqlEx.Errors.Cast<SqlError>().Any(e => TransientErrorNumbers.Contains(e.Number)); } if (ex is DbUpdateException dbEx) { return dbEx.InnerException != null && IsTransientError(dbEx.InnerException); } return ex is TimeoutException; } }
步骤2:两种集成方案二选一
方案A:将Polly挂载到EF Core自定义执行策略(仅EF仓储生效)
完全满足将Polly集成到EF执行管道的需求,无需修改现有仓储代码:
- 自定义EF执行策略:
using Microsoft.EntityFrameworkCore.Storage; public class PollyEfExecutionStrategy : ExecutionStrategy { private readonly IAsyncPolicy _policy; public PollyEfExecutionStrategy(ExecutionStrategyDependencies dependencies) : base(dependencies, maxRetryCount: 6, maxRetryDelay: TimeSpan.FromSeconds(64)) { _policy = SqlResiliencePolicy.GetCombinedPolicy(); } protected override async Task<TResult> ExecuteAsync<TState, TResult>( Func<TState, CancellationToken, Task<TResult>> operation, TState state, CancellationToken cancellationToken) { return await _policy.ExecuteAsync(async ct => await base.ExecuteAsync(operation, state, ct), cancellationToken); } protected override bool ShouldRetryOn(Exception exception) { return true; } }
- 注册自定义执行策略到DbContext:
// 在Program.cs/Startup.cs的DbContext配置中添加 services.AddDbContext<你的DbContext类名>(options => { options.UseSqlServer("Azure SQL连接字符串", sqlOpts => { sqlOpts.ExecutionStrategy(deps => new PollyEfExecutionStrategy(deps)); }); });
方案B:仓储层装饰器(同时兼容EF和Dapper仓储)
不需要修改现有仓储实现,同时对两类仓储生效,和项目其他模块的Polly使用方式保持一致:
- 定义通用仓储装饰器:
public class ResilientGenericRepositoryDecorator<T> : IGenericRepository<T> { private readonly IGenericRepository<T> _inner; private readonly IAsyncPolicy _policy; public ResilientGenericRepositoryDecorator(IGenericRepository<T> inner) { _inner = inner; _policy = SqlResiliencePolicy.GetCombinedPolicy(); } public async Task<IReadOnlyList<T>> ListAllAsync(CancellationToken cancellationToken = default) { return await _policy.ExecuteAsync(async ct => await _inner.ListAllAsync(ct), cancellationToken); } public async Task<T> GetByIdAsync(CancellationToken cancellationToken = default, int id) { return await _policy.ExecuteAsync(async ct => await _inner.GetByIdAsync(ct, id), cancellationToken); } public void Dispose() { _inner.Dispose(); } }
- 注册装饰器到DI容器:
// 注册原始仓储实现,可切换EF或Dapper版本 services.AddScoped(typeof(IGenericRepository<>), typeof(EfSqlGenericRepository<>)); // services.AddScoped(typeof(IGenericRepository<>), typeof(DapperRepository<>)); // 注册装饰器,覆盖原有IGenericRepository的解析逻辑 services.Decorate(typeof(IGenericRepository<>), typeof(ResilientGenericRepositoryDecorator<>));
注意事项
- 如果涉及多步数据库操作的事务,需要用EF执行策略的
ExecuteAsync包裹整个事务逻辑,避免部分操作成功部分失败的问题 - 降级逻辑可根据实际业务场景调整,比如读操作可返回预热缓存数据,写操作可写入本地队列后续异步补执行
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

