Edit:
In order for transaction areas to work with async-await starting with .NET 4.5.1, you can pass the TransactionScopeAsyncFlowOption.Enabled flag to its constructor:
using (var scope = new TransactionScope(... , TransactionScopeAsyncFlowOption.Enabled))
This ensures that transaction areas behave well with continuation. See Get TransactionScope for working with async / await for more details.
Note. This feature has been available since .NET 4.5.1.
Edit 2:
Well, after @Jcl's comment on BeingTransaction I searched and found this answer :
With the introduction of EF6, Microsoft recommends using the new API methods: Database.BeginTransaction() and Database.UseTransaction() . System.Transactions.TransactionScope is just the old style of writing transactional code.
But Database.BeginTransaction() is only used for database-related transaction operations , while System.Transactions.TransactionScope makes "simple C # code" also transactional .
Limitations of the new asynchronous TransactionScope functions:
To work with asynchronous methods, .NET 4.5.1 or more is required.
It cannot be used in cloud scenarios unless you are sure that you have one and only one connection (cloud scripts do not support distributed transactions).
It cannot be combined with the Database.UseTransaction() approach of the previous sections.
It will throw exceptions if you throw any kind of DDL (for example, due to a database initializer) and did not enable distributed transactions
through the MSDTC service.
It seems like a new approach starting with EF6 and above should use Database.BeginTransaction() instead of TransactionScope , given the limitations.
Finally:
This is the correct way to record transaction calls with an asynchronous transaction:
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 if your area is wrapped in a using statement, as this will take a rollback if the commit failed.
Related question: Rollback transaction Entity Framework 6
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) { } }
Do not do what you think is doing. When you execute an asynchronous method, you usually should wait for it asynchronously using the await keyword. This method:
- Uses
void as the return type. If it is an asynchronous API, it must be at least async Task . async void methods are intended only for event handlers, where this is clearly not the case. The end user is likely to expect this method, it should be converted to:
public virtual Task SaveAndCommitAsync(TEntity entity) { try { Save(entity); return _unitOfWork.SaveChangesAsync(); } catch (DbEntityValidationException e) { } }
If you want to include a transaction scope, this method should be expected:
public virtual async Task SaveAndCommitAsync(TEntity entity) { try { Save(entity); await _unitOfWork.SaveChangesAsync(); } catch (DbEntityValidationException e) { } }
The same goes for the rest of your asynchronous methods. Once the transaction exists, make sure you expect this method.
Also, do not swallow such exceptions, do something useful with them, or simply do not catch.