Entity Framework 6 个异步操作和 TranscationScope
Entity Framework 6 async operations and TranscationScope
我在Whosebug上搜索,没有找到类似的问题,有的话请指点一下。
我试图通过同步和异步操作实现一个通用的可重用存储库,但由于我对 Entity Framework 和工作单元知之甚少,我正在努力寻找实现它的正确方法。
我在 SaveAndCommit 操作上添加了一些变体,但不知道使用事务和异步执行此操作的最佳方法是什么。
----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)
这是我目前所做的
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
}
请指导我并提出实现此目标的最佳方法。
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
}
}
参考文献
MSDN Reference
Blog with more clear description
visualstudiomagazine.com For : 当您调用 SaveChanges 时,您的 none 更改将生效,直到您调用 Transaction 对象的 Commit 方法
编辑:
为了让事务范围与 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:
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.
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
This related article 阐明了新的 API
旁注:
这段代码:
public virtual void SaveAndCommitAsync(TEntity entity)
{
try
{
Save(entity);
_unitOfWork.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
}
}
没有按照您认为的那样去做。当你执行一个异步方法时,你应该通常使用await
关键字异步等待它。此方法:
- 正在使用
void
作为其 return 类型。如果这是异步API,则至少需要async Task
。 async void
方法仅适用于 事件处理程序 ,这里显然不是这种情况
最终用户可能会等待这个方法,它应该变成:
public virtual Task SaveAndCommitAsync(TEntity entity)
{
try
{
Save(entity);
return _unitOfWork.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
}
}
如果你想包含一个事务范围,那么必须等待这个方法:
public virtual async Task SaveAndCommitAsync(TEntity entity)
{
try
{
Save(entity);
await _unitOfWork.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
}
}
其余的异步方法也是如此。一旦有交易,请确保您在该方法上等待。
此外,不要像那样吞下异常,对它们做一些有用的事情,或者干脆不捕获。
我在Whosebug上搜索,没有找到类似的问题,有的话请指点一下。
我试图通过同步和异步操作实现一个通用的可重用存储库,但由于我对 Entity Framework 和工作单元知之甚少,我正在努力寻找实现它的正确方法。
我在 SaveAndCommit 操作上添加了一些变体,但不知道使用事务和异步执行此操作的最佳方法是什么。
----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)
这是我目前所做的
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
}
请指导我并提出实现此目标的最佳方法。
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
}
}
参考文献 MSDN Reference
Blog with more clear description
visualstudiomagazine.com For : 当您调用 SaveChanges 时,您的 none 更改将生效,直到您调用 Transaction 对象的 Commit 方法
编辑:
为了让事务范围与 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:
With the introduction of EF6, Microsoft recommends to use new API methods:
Database.BeginTransaction()
andDatabase.UseTransaction()
. System.Transactions.TransactionScope is just old style of writing transactional code.But
Database.BeginTransaction()
is used only for database related operations transaction, whereasSystem.Transactions.TransactionScope
makes the possible 'plain C# code' also transactional.
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
This related article 阐明了新的 API
旁注:
这段代码:
public virtual void SaveAndCommitAsync(TEntity entity)
{
try
{
Save(entity);
_unitOfWork.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
}
}
没有按照您认为的那样去做。当你执行一个异步方法时,你应该通常使用await
关键字异步等待它。此方法:
- 正在使用
void
作为其 return 类型。如果这是异步API,则至少需要async Task
。async void
方法仅适用于 事件处理程序 ,这里显然不是这种情况 最终用户可能会等待这个方法,它应该变成:
public virtual Task SaveAndCommitAsync(TEntity entity) { try { Save(entity); return _unitOfWork.SaveChangesAsync(); } catch (DbEntityValidationException e) { } }
如果你想包含一个事务范围,那么必须等待这个方法:
public virtual async Task SaveAndCommitAsync(TEntity entity)
{
try
{
Save(entity);
await _unitOfWork.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
}
}
其余的异步方法也是如此。一旦有交易,请确保您在该方法上等待。
此外,不要像那样吞下异常,对它们做一些有用的事情,或者干脆不捕获。