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

如何在EF Core 3.1中集成Polly实现数据库操作的弹性容错策略

实现方案

前提依赖

首先安装所需NuGet包:

  • Polly
  • Microsoft.EntityFrameworkCore
  • Microsoft.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执行管道的需求,无需修改现有仓储代码:

  1. 自定义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;
    }
}
  1. 注册自定义执行策略到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使用方式保持一致:

  1. 定义通用仓储装饰器:
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();
    }
}
  1. 注册装饰器到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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 23:45:02