[英]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.