Entity Framework 6 异步操作和 TranscationScope [英] Entity Framework 6 async operations and TranscationScope

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

问题描述

我在 stackoverflow 上搜索,但没有找到类似的问题,如果已经有请指出.

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

我在 SaveAndCommit 操作上添加了一些变体,但不知道使用事务和异步执行此操作的最佳方法是什么.

<块引用>

----编辑----

根据我的理解,应在执行多个操作时使用事务,但出于理解目的,我将其用于一项操作.(如有不对请指正)

这是我目前所做的

公共类服务:IService其中 TEntity : 实体{#region 构造函数和属性UnitOfWork _unitOfWork { 获取 { 返回 UnitOfWork.UnitOfWorkPerHttpRequest;} }受保护的 DbSet实体{得到 { 返回 _unitOfWork.Set();}}#endregion 构造函数和属性#region 操作公共虚拟 IQueryable可查询实体(){返回实体;}公共虚拟异步任务>WhereAsync(Expression<Func<TEntity, bool>>谓词){返回 await Entities.Where(predicate).ToListAsync();}公共虚拟 IListWhere(Expression谓词){返回 Entities.Where(predicate).ToList();}公共虚拟异步任务FirstOrDefaultAsync(Expression<Func<TEntity, bool>>谓词){返回等待 Entities.FirstOrDefaultAsync(predicate);}public virtual TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate){返回 Entities.FirstOrDefault(谓词);}公共虚拟异步任务GetByIdAsync(int id){返回等待 Entities.FindAsync(id);}公共虚拟 TEntity GetById(int id){返回 Entity.Find(id);}//改变EntityState的方法公共虚拟无效保存(TEntity实体){如果(实体.Id == 0){实体.添加(实体);}别的{_unitOfWork.Entry(entity).State = EntityState.Modified;}}#region 这里需要澄清//使用事务范围提交实体并自动处理//调用回滚,但这不是异步的,也没有任何异步//函数(或者我找不到)public virtual void SaveAndCommit(TEntity entity){使用 (var 事务 = _unitOfWork.BeginTransaction()){尝试{保存(实体);交易.提交();}catch (DbEntityValidationException e){}}}//这是异步的,但不使用事务公共虚拟异步任务 SaveAndCommitAsync(TEntity entity){尝试{保存(实体);等待 _unitOfWork.SaveChangesAsync();}catch (DbEntityValidationException e){}}//尝试混合异步和事务,但不知道它是否真的会//工作或这样做的正确方法公共虚拟异步任务 SaveAndCommitWithTransactionAsync(TEntity entity){使用 (var 事务 = _unitOfWork.BeginTransaction()){尝试{保存(实体);等待 _unitOfWork.SaveChangesAsync();}catch (DbEntityValidationException e){事务.回滚();}}}#endregion 这里需要澄清公共虚拟异步任务 DeleteAsync(TEntity entity){如果(实体==空)返回;Entity.Remove(entity);等待 _unitOfWork.SaveChangesAsync();}//所有类似的删除方法和保存公共虚拟异步任务CountAsync(Expression<Func<TEntity, bool>> predicate = null){如果(谓词!= null){返回等待 Entities.CountAsync(谓词);}返回等待 Entities.CountAsync();}#endregion 操作}

请指导我并建议实现这一目标的最佳方法.


<块引用>

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

public virtual async Task SaveAndCommitWithTransactionAsync(TEntity entity){使用 (var 事务 = _unitOfWork.BeginTransaction()){保存(实体);等待 _unitOfWork.SaveChangesAsync();//仍然没有对数据库进行任何更改交易.提交();//rollback会在dispose方法中自动调用}}

参考资料MSDN 参考

有更清晰描述的博客

visualstudiomagazine.com 对于:当你调用 SaveChanges,在调用 Transaction 对象的 Commit 方法之前,您的任何更改都不会生效

解决方案

为了使事务范围与 async-await 一起工作,从 .NET 4.5.1 开始,您可以将 TransactionScopeAsyncFlowOption.Enabled 标志传递给其构造函数:

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

这可确保事务作用域在延续时表现良好.请参阅获取 TransactionScope 以使用 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){使用 (var 事务 = _unitOfWork.BeginTransaction()){尝试{保存(实体);等待 _unitOfWork.SaveChangesAsync();交易.提交();}catch (DbEntityValidationException e){}}}

请注意,如果您的范围包含在 using 语句中,则不应调用 transaction.RollBack(),因为如果提交是不成功.

相关问题:Entity Framework 6 事务回滚

这篇相关文章更详细地介绍了新 API

旁注:

这段代码:

public virtual void SaveAndCommitAsync(TEntity entity){尝试{保存(实体);_unitOfWork.SaveChangesAsync();}catch (DbEntityValidationException e){}}

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

  1. 使用 void 作为其返回类型.如果这是一个异步 API,它需要至少 async Task.async void 方法仅适用于事件处理程序,这里显然不是这种情况
  2. 最终用户可能会等待这个方法,它应该变成:

    public virtual Task SaveAndCommitAsync(TEntity entity){尝试{保存(实体);返回 _unitOfWork.SaveChangesAsync();}catch (DbEntityValidationException e){}}

如果你想包含一个事务范围,那么必须等待这个方法:

public virtual async Task SaveAndCommitAsync(TEntity entity){尝试{保存(实体);等待 _unitOfWork.SaveChangesAsync();}catch (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.

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

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