簡體   English   中英

如何在 EF Core 中具有范圍 Dbcontext 時實現數據庫彈性?

[英]How to implement database resiliency while having a scoped Dbcontext in EF Core?

根據 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重新嘗試邏輯。 文檔說:

丟棄當前的 DbContext。 創建一個新的 DbContext 並從數據庫中恢復應用程序的 state。 通知用戶上一個操作可能沒有成功完成。

但是,在Startup.cs中,我將AppDbContext作為范圍依賴項。 為了重新嘗試方法邏輯,我需要一個新的AppDbContext實例,但注冊為作用域將不允許這樣做。

我想到的一種解決方案是使AppDbContext瞬態。 但我有一種感覺,通過這樣做,我將為自己打開一整套新問題。 有人可以幫我嗎?

保存上下文時至少有太多類型的錯誤。 第一個發生在命令執行期間。 第二個發生在提交期間(這種情況很少發生)。 即使數據已成功更新,也可能發生第二個錯誤。 所以你的代碼只處理第一種錯誤,但沒有考慮第二種錯誤。

對於第一種錯誤,您可以在命令處理程序中注入 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;
    }
  }
}

但是正如我所說,要處理這兩種錯誤,我們應該在 EFCore 中使用所謂的IExecutionStrategy 這里介紹了幾個選項。 但我認為以下內容最適合您的情況:

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();
}

請注意,我假設您的上下文通過屬性Students公開了DbSet<Student> 如果您有任何其他與連接無關的錯誤,它不會由IExecutionStrategy處理,這很有意義。 因為那是您需要修復邏輯的時候,所以重試數千次將無濟於事,並且總是以該錯誤告終。 這就是為什么我們不需要關心最初拋出的詳細異常(在我們使用IExecutionStrategy時不會暴露)。 相反,我們使用自定義異常(如我上面的代碼中所述)來通知由於某些與連接相關的問題而無法保存更改。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM