gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-05 04:52:17 26 4
gpt4 key购买 nike

根据 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/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com