Entity Framework 6 异步操作和 TranscationScope

Posted

技术标签:

【中文标题】Entity Framework 6 异步操作和 TranscationScope【英文标题】:Entity Framework 6 async operations and TranscationScope 【发布时间】:2015-03-23 21:39:53 【问题描述】:

我在***上搜索但找不到类似的问题,如果已经有请指出。

我试图实现一个具有同步和异步操作的通用可重用存储库,但我对实体框架和工作单元知之甚少,我正在努力寻找实现它的正确方法。

我在 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 Reference

Blog with more clear description

visualstudiomagazine.com 对于:当您调用 SaveChanges 时,您的任何更改都不会生效,直到您调用 Transaction 对象的 Commit 方法

【问题讨论】:

似乎是什么问题?您的问题不清楚 从我的问题:“我正在努力寻找正确的方法来实现它”我无法弄清楚如何将服务与工作单元和异步保存或删除操作一起使用。 【参考方案1】:

编辑:

为了使事务范围与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 发表评论后,我搜索并找到了this answer:

随着 EF6 的引入,微软建议使用新的 API 方法:Database.BeginTransaction()Database.UseTransaction()。 System.Transactions.TransactionScope 只是旧的写作风格 交易代码。

Database.BeginTransaction() 仅用于与数据库相关的 操作事务,而System.Transactions.TransactionScope 使可能的“纯 C# 代码”也具有事务性

TransactionScope 的新异步功能的限制:

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

它不能在云场景中使用,除非你确定你有一个 并且只有一个连接(云场景不支持分布式 交易)。

不能与Database.UseTransaction() 的方法结合使用 前面的部分。

如果您发出任何 DDL(例如,由于 a 数据库初始化器)并且没有启用分布式事务 通过 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

This related article 为新 API 提供了更多亮点

旁注:

这段代码:

public virtual void SaveAndCommitAsync(TEntity entity)

    try
    
        Save(entity);
        _unitOfWork.SaveChangesAsync();
    
    catch (DbEntityValidationException e)
    
    

没有做你认为它正在做的事情。当您执行异步方法时,您应该通常使用await 关键字异步等待它。这个方法:

    使用void 作为其返回类型。如果这是一个异步 API,它需要 至少 async Taskasync void 方法仅适用于事件处理程序,这里显然不是这种情况

    最终用户可能会等待这个方法,它应该变成:

    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)
    
    

其余的异步方法也是如此。一旦有交易,请确保您在方法上等待。

另外,不要吞下这样的异常,对它们做一些有用的事情,或者干脆不捕捉。

【讨论】:

评论不用于扩展讨论;这个对话是moved to chat。 请注意,提交是不是异步的,甚至在SaveChangesAsync 调用期间创建的隐式事务的提交也不是。所以TransactionScopeAsyncFlowOption.Enabled 允许TransactionScope 以异步方式工作(意味着事务遵循异步继续),但提交本身不是异步的。它在持续时间内阻塞系统线程。同样适用于DbContextTransaction.Commit。所有路径最终都会调用DbTransaction.Commit,这是同步的。

以上是关于Entity Framework 6 异步操作和 TranscationScope的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Entity Framework Core 模拟异步存储库

Entity Framework 6 Recipes 2nd Edition(10-10)译 - > 为TPH继承的插入更新删除操作映射到存储过程

在Oracle中使用Entity Framework 6 CodeFirst

C# ASP.NET Core Entity Framework Core 异步 ToQueryable 比较

Entity Framework技巧系列之二 - Tip 6 - 8

为啥要使用 Attach 来更新 Entity Framework 6?