In today blog post we will talk a little about a new feature that is available on EF6+ related to Transactions.
Until now, when we had to use transaction we used ‘TransactionScope’. It works great and I would say that is something that is now in our blood.
In the following example we create a new transaction and use it to commit our changes.
There is limitation related to retry policy. When we are doing multiple SaveChanges to our contexts, the retry policy would not know what data was saved or not. See below example:
The new way of using transactions is the one that is recommended by EF Team. This is the way how we should use transactions.
Until now, when we had to use transaction we used ‘TransactionScope’. It works great and I would say that is something that is now in our blood.
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
using (SqlConnection conn = new SqlConnection("..."))
{
conn.Open();
SqlCommand sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText = ...
sqlCommand.ExecuteNonQuery();
...
}
scope.Complete();
}
Starting with EF6.0 we have a new way to work with transactions. The new approach is based on Database.BeginTransaction(), Database.Rollback(), Database.Commit(). Yes, no more TransactionScope.In the following example we create a new transaction and use it to commit our changes.
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
context.Foo.Add(foo);
context.Foo.Add(foo);
context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
The new feature allow us to pass an existing transaction between different context and even reuse an existing transaction using Database.UseTransaction().using (var context = new FooContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(myTransaction);
context.Foo.Add(foo);
context.SaveChanges();
}
myTransaction.Commit();
The next tables shows what are the features supported by the new way to execute transaction in comparison with the old one.
Property
|
Database.BeginTransaction
(new feature)
|
TransactionScope
(old feature)
|
Is recommended by
Microsoft in EF6
|
Yes
|
No
|
Only database related
operation in transaction
|
Yes
|
No
|
Mixing DB operation
and C# code in the transaction
|
No
|
Yes
|
Wrap
Database.ExecuteSqlCommand in transaction if no transaction is specified
|
Yes
|
No
|
Can specify to a new
DataContext an existing transaction that will be used
|
Yes
|
No
|
Can be database
connection managed manually and specified to objects
|
Yes (to DataContext)
|
Partially Yes
|
Can the isolation
level to be controlled
|
Yes
|
Yes
|
Can execute pure SQL
queries in a custom transaction
|
Yes
|
Yes
|
Async calls supported
in a transaction
|
Yes
|
Yes (from .NET 4.5.1,
with some small limitation)
|
Where the transaction
is managed and controlled
|
By developer from code
|
In background, by the
.NET Core
|
Full control of the
transaction
|
Yes
|
Partially
|
Existing transaction
can be used
|
Yes
|
No
|
There is limitation related to retry policy. When we are doing multiple SaveChanges to our contexts, the retry policy would not know what data was saved or not. See below example:
using (var db = new FooContext())
{
using (var transaction = db.Database.BeginTransaction())
{
db.Foos.Add(new Foo());
db.SaveChanges();
db.Foos.Add(new Foo());
db.SaveChanges();
transaction.Commit();
}
}
In this case we have a workaround by suspending the retry execution strategy for that peace of code or specify a manual call execution strategy. I highly recommend to review this situations and try to create a store procedure that execute all the necessary changes in one transaction. It is safer and can be controlled easier.The new way of using transactions is the one that is recommended by EF Team. This is the way how we should use transactions.
Very Nice Article
ReplyDeleteThanks for your article, it's really clear and helpful.
ReplyDeleteHow can I, if I need to log the error like the ERROR_MESSAGE and ERROR_LINE in SQL SERVER in the catch method where the rollback is being performed?
ReplyDeleteGreat insightful article. Thanks!
ReplyDeleteCool
ReplyDeletewhy not use
ReplyDeletetry
{
transaction.Commit();
}catch(Exceptions)
{
transaction.Rollback();
}