Entity Framework 6 异步操作和 TranscationScope

15

我在stackoverflow上搜索了一下,但没有找到类似的问题,请告诉我是否已经有这样的问题。

我试图实现一个通用的可重用存储库,具有同步和异步操作,但是由于我对Entity Framework和Unit Of Work的了解很少,所以我很难找到正确的实现方式。

我已经添加了一些SaveAndCommit操作的变化,但不知道使用事务和异步操作的最佳方法。

----编辑----

据我理解,当执行多个操作时应该使用事务,但为了理解的目的,我将其用于一个操作。(如果我错了,请纠正我)

这是我目前所做的。

public class Service<TEntity> : IService<TEntity>
    where TEntity : Entity
{
    #region Constructor and Properties

    UnitOfWork _unitOfWork { get { return UnitOfWork.UnitOfWorkPerHttpRequest; } }

    protected DbSet<TEntity> Entities
    {
        get { return _unitOfWork.Set<TEntity>(); }
    }

    #endregion Constructor and Properties


    #region Operations

    public virtual IQueryable<TEntity> QueryableEntities()
    {
        return Entities;
    }

    public virtual async Task<IList<TEntity>> WhereAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await Entities.Where(predicate).ToListAsync();
    }

    public virtual IList<TEntity> Where(Expression<Func<TEntity, bool>> predicate)
    {
        return Entities.Where(predicate).ToList();
    }

    public virtual async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await Entities.FirstOrDefaultAsync(predicate);
    }

    public virtual TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
    {
        return Entities.FirstOrDefault(predicate);
    }

    public virtual async Task<TEntity> GetByIdAsync(int id)
    {
        return await Entities.FindAsync(id);
    }

    public virtual TEntity GetById(int id)
    {
        return Entities.Find(id);
    }

    // Method to the change the EntityState
    public virtual void Save(TEntity entity)
    {
        if (entity.Id == 0)
        {
            Entities.Add(entity);
        }
        else
        {
            _unitOfWork.Entry(entity).State = EntityState.Modified;
        }
    }

    #region Need clarification here

    // Uses transaction scope to commit the entity and dispose automatically
    // call rollback but this is not async and don't have any async
    // functions (Or I could not find)
    public virtual void SaveAndCommit(TEntity entity)
    {
        using (var transaction = _unitOfWork.BeginTransaction())
        {
            try
            {
                Save(entity);
                transaction.Commit();
            }
            catch (DbEntityValidationException e)
            {
            }
        }
    }

    // This is asynchronous but don't uses transaction
    public virtual async Task SaveAndCommitAsync(TEntity entity)
    {
        try
        {
            Save(entity);
            await _unitOfWork.SaveChangesAsync();
        }
        catch (DbEntityValidationException e)
        {
        }
    }

    // Tried to mix async and transaction but don't know if it will actually         
    // work or correct way of doing this
    public virtual async Task SaveAndCommitWithTransactionAsync(TEntity entity)
    {
        using (var transaction = _unitOfWork.BeginTransaction())
        {
            try
            {
                Save(entity);
                await _unitOfWork.SaveChangesAsync();
            }
            catch (DbEntityValidationException e)
            {
                transaction.Rollback();
            }
        }
    }

    #endregion Need clarification here
    
    public virtual async Task DeleteAsync(TEntity entity)
    {
        if (entity == null) return;

        Entities.Remove(entity);
        await _unitOfWork.SaveChangesAsync();
    }

    //All similar methods for delete as for Save

    public virtual async Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate = null)
    {
        if (predicate != null)
        {
            return await Entities.CountAsync(predicate);
        }

        return await Entities.CountAsync();
    }

    #endregion Operations

}

请指导我并建议最佳实现方法。


现在,似乎使用异步调用实现事务范围的正确方法是

public virtual async Task SaveAndCommitWithTransactionAsync(TEntity entity)
    {
        using (var transaction = _unitOfWork.BeginTransaction())
        {
                Save(entity);
                await _unitOfWork.SaveChangesAsync();

                // Still no changes made to database
                transaction.Commit();
            
               //Rollback will automatically be called by using in dispose method
        }
    }

参考资料 MSDN 参考文献

更清晰的描述博客

visualstudiomagazine.com 对于:当您调用 SaveChanges 时,只有在调用 Transaction 对象的 Commit 方法后,您的更改才会生效。


有什么问题吗?从你的问题中无法确定。 - Yuval Itzchakov
从我的问题中: “我正在努力找到正确的实现方式”我无法弄清如何使用带有工作单元和异步保存或删除操作的服务。 - Sandeep Kumar
1个回答

28

编辑:

为了使事务范围与async-await一起工作,在.NET 4.5.1及以上版本中,您可以在其构造函数中传入TransactionScopeAsyncFlowOption.Enabled标志:

using (var scope = new TransactionScope(... ,
  TransactionScopeAsyncFlowOption.Enabled))

这能确保事务范围与延续操作的良好表现。 详情请参见Get TransactionScope to work with async/await

请注意,此功能自.NET 4.5.1版本起可用。

编辑2:

好的,在@Jcl关于BeingTransaction的评论之后,我搜索并找到此答案:

随着EF6的推出,微软建议使用新的API方法Database.BeginTransaction()Database.UseTransaction()。System.Transactions.TransactionScope只是编写事务代码的旧式方式。

但是Database.BeginTransaction()仅用于与数据库相关的操作事务,而System.Transactions.TransactionScope则使“普通C#代码”也具有事务性。

TransactionScope的新异步特性的限制:

  • 需要.NET 4.5.1或更高版本才能与异步方法一起使用。

  • 除非您确定只有一个连接(云场景不支持分布式事务),否则无法在云场景中使用。

  • 无法与前几节的Database.UseTransaction()方法组合使用。

  • 如果您发出任何DDL(例如,由于数据库初始化器)并且未通过MSDTC服务启用分布式事务,则会引发异常。

鉴于这些限制,似乎EF6及以上版本的新方法是使用Database.BeginTransaction()而不是TransactionScope

总之:

这是编写异步事务作用域数据库调用的正确方法:

public virtual async Task SaveAndCommitWithTransactionAsync(TEntity entity)
{
    using (var transaction = _unitOfWork.BeginTransaction())
    {
        try
        {
            Save(entity);
            await _unitOfWork.SaveChangesAsync();

            transaction.Commit();
        }
        catch (DbEntityValidationException e)
        {
        }
    }
}
请注意,在使用using语句包装作用域的情况下,不应调用transaction.RollBack(),因为如果提交不成功,它会自动回滚。
相关问题: Entity Framework 6 transaction rollback 这篇相关文章详细介绍了新API。
注:以下是附带代码:
public virtual void SaveAndCommitAsync(TEntity entity)
{
    try
    {
        Save(entity);
        _unitOfWork.SaveChangesAsync();
    }
    catch (DbEntityValidationException e)
    {
    }
}

它做的不是你想要的事情。当你执行一个异步方法时,通常应该使用await关键字进行异步等待。这个方法:

  1. void 作为返回类型。如果这是一个异步 API,则需要至少是 async Task。而 async void 方法只适用于事件处理程序 ,而这显然不是这里的情况。
  2. 最终用户可能会在此方法上等待,因此应将其转换为:

public virtual Task SaveAndCommitAsync(TEntity entity)
{
   try
   {
       Save(entity);
       return _unitOfWork.SaveChangesAsync();
   }
   catch (DbEntityValidationException e)
   {
   }
}

如果您想包含一个Transaction Scope,那么必须等待此方法:

public virtual async Task SaveAndCommitAsync(TEntity entity)
{
    try
    {
        Save(entity);
        await _unitOfWork.SaveChangesAsync();
    }
    catch (DbEntityValidationException e)
    {
    }
}

同样适用于您的其他异步方法。一旦有事务,请确保在方法上等待。

此外,不要像那样忽略异常,要对其进行有用的处理,或者干脆不要捕获。


评论不适合进行长时间的讨论;此对话已被移至聊天室 - Flexo
请注意,提交操作不是异步的,即使是在SaveChangesAsync调用期间创建的隐式事务的提交也不是异步的。 因此,启用TransactionScopeAsyncFlowOption.Enabled允许TransactionScope与异步工作(这意味着事务会跟随异步继续执行),但提交本身不是异步的。 它会阻塞系统线程一段时间。 DbContextTransaction.Commit也是如此。 所有路径最终都会调用同步的DbTransaction.Commit - MikeJansen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接