c# - 如何在 EF Core 中具有作用域 Dbcontext 的同时实现数据库弹性?

标签 c# asp.net-core dependency-injection ef-core-5.0 resiliency

根据 CQRS 模式,我有一个简单的命令如下:

public sealed class EditPersonalInfoCommandHandler : ICommandHandler<EditPersonalInfoCommand> {

        private readonly AppDbContext _context;

        public EditPersonalInfoCommandHandler(AppDbContext context) {
            _context = context;
        }

        public Result Handle(EditPersonalInfoCommand command) {
            var studentRepo = new StudentRepository(_context);
            Student student = studentRepo.GetById(command.Id);
            if (student == null) {
                return Result.Failure($"No Student found for Id {command.Id}");
            }

            student.Name = command.Name;
            student.Email = command.Email;

            _context.SaveChanges();
            return Result.Success();
        }

}

现在我需要尝试 _context.SaveChanges() 最多 5 次,如果失败并出现异常。为此,我可以简单地在方法中使用一个 for 循环:

for(int i = 0; i < 5; i++) {
    try {
        //required logic
    } catch(SomeDatabaseException e) {
        if(i == 4) {
           throw;
        }
    }
}

要求是将方法作为一个单元来执行。问题是,一旦 _context.SaveChanges() 抛出异常,就不能使用相同的 _context 重新尝试逻辑。文档说:

Discard the current DbContext. Create a new DbContext and restore the state of your application from the database. Inform the user that the last operation might not have been completed successfully.

但是,在 Startup.cs 中,我将 AppDbContext 作为作用域依赖项。为了重新尝试方法逻辑,我需要一个 AppDbContext 的新实例,但注册为作用域将不允许这样做。

我想到的一个解决方案是使 AppDbContext 成为 transient 的。但我有一种感觉,这样做会为我自己打开一整套新问题。谁能帮我解决这个问题?

最佳答案

保存上下文时至少有太多错误。第一个发生在命令执行期间。第二次发生在提交期间(这种情况很少发生)。 即使数据已成功更新,也可能会发生第二个错误。所以您的代码只处理第一种错误,而没有考虑第二种错误。

对于第一种错误,您可以在命令处理程序中注入(inject) DbContext 工厂或使用 IServiceProvider直接地。这是一种反模式,但在这种情况下我们别无选择,如下所示:

readonly IServiceProvider _serviceProvider;
public EditPersonalInfoCommandHandler(IServiceProvider serviceProvider) {
        _serviceProvider = serviceProvider;
}

for(int i = 0; i < 5; i++) {
  try {
    using var dbContext = _serviceProvider.GetRequiredService<AppDbContext>();
    //consume the dbContext
    //required logic
  } catch(SomeDatabaseException e) {
    if(i == 4) {
       throw;
    }
  }
}

但是正如我所说,要处理这两种错误,我们应该使用所谓的 IExecutionStrategy在 EFCore 中。介绍了几个选项here .但我认为以下内容最适合您的情况:

public Result Handle(EditPersonalInfoCommand command) {
    var strategy = _context.Database.CreateExecutionStrategy();
    
    var studentRepo = new StudentRepository(_context);
    Student student = studentRepo.GetById(command.Id);
    if (student == null) {
        return Result.Failure($"No Student found for Id {command.Id}");
    }

    student.Name = command.Name;
    student.Email = command.Email;
    const int maxRetries = 5;
    int retries = 0;
    strategy.ExecuteInTransaction(_context,
                                  context => {
                                      if(++retries > maxRetries) {
                                         //you need to define your custom exception to be used here
                                         throw new CustomException(...);
                                      }
                                      context.SaveChanges(acceptAllChangesOnSuccess: false);
                                  },
                                  context => context.Students.AsNoTracking()
                                                    .Any(e => e.Id == command.Id && 
                                                              e.Name == command.Name &&
                                                              e.Email == command.Email));

    _context.ChangeTracker.AcceptAllChanges();
    return Result.Success();
}

请注意,我想您的上下文公开了一个 DbSet<Student>通过属性 Students . 如果您有任何其他与连接无关的错误,IExecutionStrategy 将不会处理。这很有意义。因为那是您需要修复逻辑的时候,所以重试数千次无济于事,而且总是以该错误告终。这就是为什么我们不需要关心详细的最初抛出的异常(当我们使用 IExecutionStrategy 时不会暴露)。相反,我们使用自定义异常(如我上面的代码中所述)来通知由于某些与连接相关的问题而导致保存更改失败。

关于c# - 如何在 EF Core 中具有作用域 Dbcontext 的同时实现数据库弹性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66573522/

相关文章:

c# - 是否可以使命令提示符上的输出变成不同的颜色? C# 编程

c# - 在C#中发布: Identifier expected

c# - 隐藏和显示面板的Javascript代码

带有 IIS 的 ASP.NET Core - 不允许使用 HTTP 动词

c# - 如何注入(inject) Web api IHttpRouteConstraint?

c# - 使用 lambda,如何将现有函数应用于列表的所有元素?

c# - 如何检查 MVC Core 配置文件中的某个部分是否存在?

C# 使用动态构造的事件处理程序处理 DDD 域事件

android - 如何注入(inject)动态创建的用例(android、整洁的架构、dagger2)

asp.net-core - 如何在 ASP.NET Core 中使 Controller 作用域或单例而不是 transient ?