我们正在使用ExecutionStrategy并在我们的db上下文中使用这个帮助器方法:
public Task<T> ExecuteWithinTransactionAsync<T>(Func<IDbContextTransaction, Task<T>> operation, string operationInfo)
{
int counter = 0;
return Database.CreateExecutionStrategy().ExecuteAsync(RunOperationWithinTransaction);
async Task<T> RunOperationWithinTransaction()
{
counter++;
if (counter > 1)
{
Logger.Log(LogLevel.Warn, $"Executing ({counter}. time) transaction for {operationInfo}.");
ClearChangeTracker();
}
using (var transaction = await Database.BeginTransactionAsync(IsolationLevel.Serializable))
{
return await operation.Invoke(transaction);
}
}
}
我们在调用复杂/脆弱的业务逻辑时使用ExecuteWithinTransactionAsync
,这应该可靠地在可序列化的事务中执行。我们正在使用Postgres,因此可能会因为序列化问题导致我们的事务中止。执行策略检测它并重试该操作。这很好用。但是EF仍然保留了之前执行的旧缓存。这就是为什么我们引入ClearChangeTracker
看起来像这样的原因:
private void ClearChangeTracker()
{
ChangeTracker.DetectChanges();
foreach (var entity in ChangeTracker.Entries().ToList())
{
entity.State = EntityState.Detached;
}
}
这似乎已经正常工作,直到我们找到一个不再起作用的案例。当我们将新实体添加到导航属性列表时,下次尝试时不会删除这些实体。例如
var parent = context.Parents.FirstOrDefault(p => p.Id == 1);
if (parent.Children.Any())
{
throw new Exception("Parent already has a child"); // This exception is thrown on the second try
}
parent.Children.Add(new Child());
context.SaveChangesAsync();
因此,如果最后一行context.SaveChangesAsync()
失败,并且重新运行整个操作,parent.Children
已经包含parent.Children.Add(new Child());
中添加的新子项,我找不到任何方法从EF中删除该项目。
但是,如果我们删除了支票(if (parent.Children.Any())
),如果该商品已经存在,只是尝试再次添加,则只会在数据库中存储一次。
我试图找出如何正确清除DbContext,但大多数时候,答案只是创建一个新的DbContext。但是,这不是一个选项,因为ExecutionStrategy需要DbContext。这就是为什么我想知道,在每次重试时使用ExecutionStrategy以及使用干净的DbContext的建议方法是什么。
答案 0 :(得分:1)
在ef-core 2.0.0中,引入了此new feature DbContext
池。为了使其正常工作,DbContext
实例现在能够重置其内部状态,因此它们可以作为“新”发布。可以像这样调用重置方法(在DbContext
内):
((IDbContextPoolable)this.ResetState();
因此,如果您可以升级到ef-core 2.0.0,那就去吧。不仅要从这个新功能中受益,它还在很多方面都比较成熟。
免责声明:此方法仅供内部使用,因此API可能会在将来发生变化。