如何正确使用ExecutionStrategy?

时间:2017-10-11 10:25:05

标签: c# postgresql entity-framework entity-framework-core

我们正在使用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的建议方法是什么。

进一步的技术细节

  • EF Core版本:1.1.2
  • 数据库提供程序:Npgsql.EntityFrameworkCore.PostgreSQL(1.1.1)
  • 操作系统:Windows 10,Linux中的Dockerized

1 个答案:

答案 0 :(得分:1)

在ef-core 2.0.0中,引入了此new feature DbContext池。为了使其正常工作,DbContext实例现在能够重置其内部状态,因此它们可以作为“新”发布。可以像这样调用重置方法(在DbContext内):

((IDbContextPoolable)this.ResetState();

因此,如果您可以升级到ef-core 2.0.0,那就去吧。不仅要从这个新功能中受益,它还在很多方面都比较成熟。

免责声明:此方法仅供内部使用,因此API可能会在将来发生变化。