实体框架6异步操作和TranscationScope [英] Entity Framework 6 async operations and TranscationScope

查看:234
本文介绍了实体框架6异步操作和TranscationScope的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我搜索的计算器,但无法找到一个类似的问题,请点我,如果已经有一个。

我试图实现与两个同步和异步操作的一般可重复使用的库,但我与实体框架和工作单元一知半解,我在努力寻找实现它的正确方法。

我添加的SaveAndCommit操作一些变化,但不知道什么是交易,异步做到这一点的最好办法。


  

---- ----编辑


  
  

根据当执行多个操作应使用我的理解交易,但理解的目的我用了一个操作。 (请纠正我,如果我错了)


这是我迄今所做

 公共类服务与LT; TEntity> :IService< TEntity>
    其中,TEntity:实体
{
    #地区的构造及性能    的UnitOfWork _unitOfWork {{返回UnitOfWork.UnitOfWorkPerHtt prequest; }}    保护DbSet< TEntity>实体
    {
        {返回_unitOfWork.Set< TEntity>(); }
    }    #endregion构造及性能
    #区域运营    公共虚拟的IQueryable< TEntity> QueryableEntities()
    {
        返回实体;
    }    公共虚拟异步任务<&IList的LT; TEntity>> WhereAsync(前pression<&Func键LT; TEntity,布尔>> predicate)
    {
        返回等待Entities.Where(predicate).ToListAsync();
    }    公共虚拟的IList< TEntity>凡(前pression<&Func键LT; TEntity,布尔>> predicate)
    {
        返回Entities.Where(predicate).ToList();
    }    公共虚拟异步任务< TEntity> FirstOrDefaultAsync(前pression<&Func键LT; TEntity,布尔>> predicate)
    {
        返回等待Entities.FirstOrDefaultAsync(predicate);
    }    公共虚拟TEntity FirstOrDefault(前pression<&Func键LT; TEntity,布尔>> predicate)
    {
        返回Entities.FirstOrDefault(predicate);
    }    公共虚拟异步任务< TEntity> GetByIdAsync(INT ID)
    {
        返回等待Entities.FindAsync(ID);
    }    公共虚拟TEntity GetById(INT ID)
    {
        返回Entities.Find(ID);
    }    //方法的变化EntityState
    公共虚拟无效保存(TEntity实体)
    {
        如果(entity.Id == 0)
        {
            Entities.Add(实体);
        }
        其他
        {
            _unitOfWork.Entry(实体).STATE = EntityState.Modified;
        }
    }    #REGION这里需要澄清    //使用事务范围提交实体,并自动配置
    //调用回滚但这不是异步,并且没有任何异步
    //函数(或我没有找到)
    公共虚拟无效SaveAndCommit(TEntity实体)
    {
        使用(VAR交易= _unitOfWork.BeginTransaction())
        {
            尝试
            {
                保存(实体);
                器transaction.commit();
            }
            赶上(DbEntityValidationException E)
            {
            }
        }
    }    //这是异步的,但不要使用事务
    公共虚拟异步任务SaveAndCommitAsync(TEntity实体)
    {
        尝试
        {
            保存(实体);
            等待_unitOfWork.SaveChangesAsync();
        }
        赶上(DbEntityValidationException E)
        {
        }
    }    //尝试混合异步和交易,但不知道这是否会实际
    //工作还是这样做的正确方法
    公共虚拟异步任务SaveAndCommitWithTransactionAsync(TEntity实体)
    {
        使用(VAR交易= _unitOfWork.BeginTransaction())
        {
            尝试
            {
                保存(实体);
                等待_unitOfWork.SaveChangesAsync();
            }
            赶上(DbEntityValidationException E)
            {
                transaction.Rollback();
            }
        }
    }    #endregion这里需要澄清    公共虚拟异步任务DeleteAsync(TEntity实体)
    {
        如果(实体== NULL)回报;        Entities.Remove(实体);
        等待_unitOfWork.SaveChangesAsync();
    }    //为删除作为全部保存类似的方法    公共虚拟异步任务< INT> CountAsync(前pression<&Func键LT; TEntity,布尔>> predicate = NULL)
    {
        如果(predicate!= NULL)
        {
            返回等待Entities.CountAsync(predicate);
        }        返回等待Entities.CountAsync();
    }    #endregion操作}

请指引我,并提出实现这一目标的最佳途径。



  

现在看来,实现与异步调用的事务范围内正确的方法是


  public虚拟异步任务SaveAndCommitWithTransactionAsync(TEntity实体)
    {
        使用(VAR交易= _unitOfWork.BeginTransaction())
        {
                保存(实体);
                等待_unitOfWork.SaveChangesAsync();                //仍数据库所做的任何更改
                器transaction.commit();               //回滚会自动使用dispose方法被调用
        }
    }

参考
MSDN参考

更明确的说明博客

visualstudiomagazine.com 有关:当你打电话的SaveChanges,没有您的更改将直到调用事务对象的Commit方法生效


解决方案

编辑:

为了让交易范围与共同努力等待异步,从.NET开始4.5.1可以在 TransactionScopeAsyncFlowOption.Enabled通标志,它的构造:

 使用(VAR范围=新的TransactionScope(...
  TransactionScopeAsyncFlowOption.Enabled))

这可以确保在交易范围与延续很好的表现。
请参见获取的TransactionScope与异步/的await 的工作更多。

请注意这个功能,因为.NET 4.5.1起可用。

编辑2:

好吧,在 BeingTransaction @Jcl评论后,我搜查,发现这回答


  

通过引进EF6的,微软建议使用新的API
  方法: Database.BeginTransaction() Database.UseTransaction()
  System.Transactions.TransactionScope只是写作的旧式
  交易code。


  
  

Database.BeginTransaction()使用的仅适用于相关数据库
  操作交易
,而 System.Transactions.TransactionScope
  使可能的纯C#code也交易


的新异步功能限制的TransactionScope


  • 需要.NET 4.5.1或更高版本使用异步方法来工作。


  • 它不能在云计算场景中使用,除非你确信你有一个
    只有一个连接(云方案不支持分布式结果
    交易)。


  • 它不能用的 Database.UseTransaction()办法相结合
    在previous部分。


  • 如果您发出任何DDL(例如它会抛出,因为结果的例外
    数据库初始化器),并没有启用分布式事务处理结果
    通过MSDTC服务。


这似乎是新的方法开始EF6及以上为使用 Database.BeginTransaction()而不是的TransactionScope 后,给出的限制。

总之:

这是写异步交易有道范围分贝的呼叫:

  public虚拟异步任务SaveAndCommitWithTransactionAsync(TEntity实体)
{
    使用(VAR交易= _unitOfWork.BeginTransaction())
    {
        尝试
        {
            保存(实体);
            等待_unitOfWork.SaveChangesAsync();            器transaction.commit();
        }
        赶上(DbEntityValidationException E)
        {
        }
    }
}

注意 transaction.RollBack()不应该叫的情况下您的范围被包裹在一个使用语句,因为它会回滚,如果提交不成功。

一个相关的问题:实体框架6事务回滚

的相关文章揭示了新的API越光

边注:

这件code的:

 公共虚拟无效SaveAndCommitAsync(TEntity实体)
{
    尝试
    {
        保存(实体);
        _unitOfWork.SaveChangesAsync();
    }
    赶上(DbEntityValidationException E)
    {
    }
}

是不是做你觉得它在做什么。当您执行这是异步的方法,你应该的一般的异步就可以使用的await 关键字等。这个方法:


  1. 中使用无效作为它的返回类型。如果这是一个异步API,它需要的至少异步任务异步无效方法仅针对精神疾病的事件处理程序的,其中这显然不是这里的情况

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

     公共虚拟任务SaveAndCommitAsync(TEntity实体)
    {
       尝试
       {
           保存(实体);
           返回_unitOfWork.SaveChangesAsync();
       }
       赶上(DbEntityValidationException E)
       {
       }
    }


如果您要包含的交易范围的,则此方法必须被期待已久的:

  public虚拟异步任务SaveAndCommitAsync(TEntity实体)
{
    尝试
    {
        保存(实体);
        等待_unitOfWork.SaveChangesAsync();
    }
    赶上(DbEntityValidationException E)
    {
    }
}

也是一样的,你的异步方法的其余部分。一旦交易是存在的,请确保您等待的方法。

另外,不要咽下这样的例外,做些有益的事与他们,或者干脆不抓。

I search on stackoverflow but could not find a similar question, please point me if there is already one.

I was trying to implement a generic reusable repository with both sync and async operations but with my little knowledge with Entity Framework and Unit Of Work I'm struggling to find the correct way to implement it.

I have added some variations on SaveAndCommit operation but don't know what is the best way to do it with transaction and async.

----Edit----

As per my understanding transactions should be used when more than one operations is performed but for understanding purposes I used it for one operation. (Please correct me if I'm wrong)

This is what I have done so far

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

}

Please guide me and suggest the best way to achieve this.


Now it seems that the correct way to implement a transaction scope with async call would be

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
        }
    }

References MSDN Reference

Blog with more clear description

visualstudiomagazine.com For : when you call SaveChanges, none of your changes will take effect until you call the Transaction object's Commit method

解决方案

Edit:

In order for transaction scopes to work together with async-await, starting from .NET 4.5.1 you can pass in a TransactionScopeAsyncFlowOption.Enabled flag to its constructor:

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

This makes sure that the transaction scopes behaves nicely with continuations. See Get TransactionScope to work with async / await for more.

Note this feature is available since .NET 4.5.1 onward.

Edit 2:

Okay, after @Jcl comment on BeingTransaction, i searched and found this answer:

With the introduction of EF6, Microsoft recommends to use new API methods: Database.BeginTransaction() and Database.UseTransaction(). System.Transactions.TransactionScope is just old style of writing transactional code.

But Database.BeginTransaction() is used only for database related operations transaction, whereas System.Transactions.TransactionScope makes the possible 'plain C# code' also transactional.

Limitations of new asynchronous features of TransactionScope:

  • Requires .NET 4.5.1 or greater to work with asynchronous methods.

  • It cannot be used in cloud scenarios unless you are sure you have one and only one connection (cloud scenarios do not support distributed
    transactions).

  • It cannot be combined with the Database.UseTransaction() approach of the previous sections.

  • It will throw exceptions if you issue any DDL (e.g. because of a
    Database Initializer) and have not enabled distributed transactions
    through the MSDTC Service.

It seems like the new approach starting EF6 and above is to use Database.BeginTransaction() instead of TransactionScope, given the limitations.

To conclude:

This is the proper way to write async transaction scoped db calls:

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

            transaction.Commit();
        }
        catch (DbEntityValidationException e)
        {
        }
    }
}

Note that transaction.RollBack() should not be called in case your scope is wrapped in a using statement, as it will take of the rollback if the commit was unsuccessful.

A related question: Entity Framework 6 transaction rollback

This related article sheds more light on the new API

Side note:

This piece of code:

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

Isn't doing what you think it's doing. When you execute a method which is asynchronous, you should usually asynchronously wait on it using the await keyword. This method:

  1. Is using void as its return type. If this is an asynchronous API, it needs to be at least async Task. async void methods are only ment for event handlers, where this clearly isn't the case here
  2. The end user will probably be awaiting on this method, it should be turned into:

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

If you want to include a Transaction Scope, then this method must be awaited:

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

Same goes for the rest of your asynchronous methods. Once a transaction is there, make sure you await on the method.

Also, don't swallow exceptions like that, do something useful with them, or simply don't catch.

这篇关于实体框架6异步操作和TranscationScope的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆