【问题标题】:Define ExecutionStrategy at configuration level in EF core在 EF 核心的配置级别定义 ExecutionStrategy
【发布时间】:2022-06-29 17:28:00
【问题描述】:

我确实有一个使用 EF 核心连接到 Azure SQL 的应用程序。 我们面临弹性故障,添加 EnableRetryOnFailure() 是我配置的解决方案。

services.AddEntityFrameworkSqlServer()
    .AddDbContext<jmasdbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DataContext"), sqlServerOptionsAction: sqlActions =>
    {
        sqlActions.EnableRetryOnFailure(
            maxRetryCount: 10,
            maxRetryDelay: TimeSpan.FromSeconds(5),
            errorNumbersToAdd: null);
    }), ServiceLifetime.Transient);

现在,当我们遇到如下 BeginTransaction 抛出错误时,这个会失败

"配置的执行策略 'SqlServerRetryingExecutionStrategy' 不支持用户启动 交易。使用返回的执行策略 'DbContext.Database.CreateExecutionStrategy()' 执行所有 事务中的操作作为可重试单元。”

我查看了 MS 文档,他们建议使用 ExecuteAsync 手动定义执行策略“https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-resilient -entity-framework-core-sql-connections"

这变得很痛苦,因为我们确实有超过 25 个地方进行这些交易。

我尝试在 DbContext 级别使用自定义 ExecutionStrategy 但这没有帮助

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured && !string.IsNullOrEmpty(ConnectionString))
    {
        optionsBuilder.UseSqlServer(ConnectionString, options =>
        {
            options.ExecutionStrategy((dependencies) =>
            {
                return new SqlServerRetryingExecutionStrategy(dependencies, maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(5), errorNumbersToAdd: new List<int> { 4060 });
            });
        });
    }
}

有没有办法在全球范围内定义它?我们不希望每个操作都采用不同的策略,每当出现故障时,我们希望它完全回滚并从头开始。

【问题讨论】:

  • 请编辑您的问题并发布代码而不是图片。无法复制图像或将图像编入索引以供搜索。
  • 感谢@SvyatoslavDanyliv,我添加了代码而不是图像。

标签: sql-server asp.net-core entity-framework-core azure-sql-database


【解决方案1】:

"配置的执行策略'SqlServerRetryingExecutionStrategy'不支持用户发起的事务。使用'DbContext.Database.CreateExecutionStrategy()'返回的执行策略将事务中的所有操作作为可重试单元执行。"

  • 如果您尝试使用 EF 执行策略(重试策略)运行该事务并从多个 DbContexts 调用 SaveChanges,则会收到类似的异常

  • You could manually invoke the execution strategy by using delegates.

  • 此错误表明您的数据库上下文服务已设置为在连接丢失时重试连接到数据库服务器。但是,在将上下文/对象保存到数据库时发生连接断开,导致错误,因为无法使用“执行策略”将失败的提交对象重新提交到数据库。

  • 使用CreateExecutionStrategy() 定义执行策略,委托封装所有数据库操作

var strategy = db.Database.CreateExecutionStrategy();

手动调用执行策略

var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
  {
    using (var dbContextTransaction = _context.Database.BeginTransaction())
    {
        // Your code here                    
        dbContextTransaction.Commit();
    }
    catch (Exception ex)
    {
        dbContextTransaction.Rollback();
        throw;
    }
  }
});

【讨论】:

  • @HarsshithaVeeramall,是的,这就是我所使用的。我的问题是“有没有办法在全球范围内定义这个?我们不希望每个操作都有不同的策略,每当出现故障时,我们希望完全回滚并从头开始。” ,问题是在进行交易的每个地方都使用,但我想要一种在全局级别定义的方法,如果有的话。
【解决方案2】:

我创建了一个库,使您能够通过已配置的执行策略和扩展方法对 SaveChanges/SaveChangesAsync 具有弹性。

先安装ResilientSaveChanges.EFCore,然后在你的应用启动时进行配置,例如:

ResilientSaveChangesConfig.Logger = _logger;
ResilientSaveChangesConfig.LoggerWarnLongRunning = 3_000;
ResilientSaveChangesConfig.ConcurrentSaveChangesLimit = 5;

然后设置你的 MySQL 执行策略,例如:

public static class Constants
{
    public const int MAX_RETRY_COUNT = 10;
    public const int MAX_RETRY_DELAY_SECONDS = 6;
    public const int COMMAND_TIMEOUT = 120;
}

public class MyExecutionStrategy : ExecutionStrategy
{
    public MyExecutionStrategy(MyDbContext context) : base(
        context,
        Constants.MAX_RETRY_COUNT,
        TimeSpan.FromSeconds(Constants.MAX_RETRY_DELAY_SECONDS))
    { }

    public MyExecutionStrategy(ExecutionStrategyDependencies dependencies) : base(
        dependencies,
        Constants.MAX_RETRY_COUNT,
        TimeSpan.FromSeconds(Constants.MAX_RETRY_DELAY_SECONDS))
    { }

    public MyExecutionStrategy(MyDbContext context, int maxRetryCount, TimeSpan maxRetryDelay) : base(
        context,
        maxRetryCount,
        maxRetryDelay)
    { }

    protected override bool ShouldRetryOn([NotNull] Exception exception)
    {
        if (exception is MySqlException mySqlException)
        {
            if (mySqlException.IsTransient)
            {
                Debug.WriteLine($"MySqlException transient error detected. Retrying in {Constants.MAX_RETRY_DELAY_SECONDS} seconds");
                return true;
            }
            Debug.WriteLine($"Non-transient MySqlException detected.");
            return false;
        }

        if (exception is DbUpdateException)
        {
            Debug.WriteLine($"DbUpdateException detected. Retrying in {Constants.MAX_RETRY_DELAY_SECONDS} seconds");
            return true;
        }

        Debug.WriteLine($"Error that won't be retried. Type is {exception.GetType()}");
        return false;
    }
}

然后利用你的执行策略,例如这样的:

services.AddPooledDbContextFactory<MyDbContext>(options =>
{
    options.UseMySql(
        Configuration.GetConnectionString("DefaultConnection"),
        "8.0.29",
        options =>
        {
            options.EnableRetryOnFailure(
                Constants.MAX_RETRY_COUNT, 
                TimeSpan.FromSeconds(Constants.MAX_RETRY_DELAY_SECONDS),
                null);
            options.CommandTimeout(Constants.COMMAND_TIMEOUT);
            options.ExecutionStrategy(s => new MyExecutionStrategy(s));
        }
    ).EnableDetailedErrors();
});

最后只需将您的context.SaveChanges();await context.SaveChangesAsync(); 分别替换为context.ResilientSaveChanges();context.ResilientSaveChangesAsync();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-13
    • 1970-01-01
    • 2021-08-22
    • 1970-01-01
    相关资源
    最近更新 更多