Working with Transactions (EF6 Onwards)

This document will describe using transactions in EF6 including the enhancements we have added since EF5 to make working with transactions easy.

What EF does by default

In all versions of Entity Framework, whenever you execute SaveChanges() to insert, update or delete on the database the framework will wrap that operation in a transaction. This transaction lasts only long enough to execute the operation and then completes. When you execute another such operation a new transaction is started.

Starting with EF6 Database.ExecuteSqlCommand() by default will wrap the command in a transaction if one was not already present. There are overloads of this method that allow you to override this behavior if you wish. Also in EF6 execution of stored procedures included in the model through APIs such as ObjectContext.ExecuteFunction() does the same (except that the default behavior cannot at the moment be overridden).

In either case, the isolation level of the transaction is whatever isolation level the database provider considers its default setting. By default, for instance, on SQL Server this is READ COMMITTED.

Entity Framework does not wrap queries in a transaction.

This default functionality is suitable for a lot of users and if so there is no need to do anything different in EF6; just write the code as you always did.

However some users require greater control over their transactions – this is covered in the following sections.

How the APIs work

Prior to EF6 Entity Framework insisted on opening the database connection itself (it threw an exception if it was passed a connection that was already open). Since a transaction can only be started on an open connection, this meant that the only way a user could wrap several operations into one transaction was either to use a TransactionScope or use the ObjectContext.Connection property and start calling Open() and BeginTransaction() directly on the returned EntityConnection object. In addition, API calls which contacted the database would fail if you had started a transaction on the underlying database connection on your own.

Note: The limitation of only accepting closed connections was removed in Entity Framework 6. For details, see Connection Management (EF6 Onwards).

Starting with EF6 the framework now provides:

  1. Database.BeginTransaction() : An easier method for a user to start and complete transactions themselves within an existing DbContext – allowing several operations to be combined within the same transaction and hence either all committed or all rolled back as one. It also allows the user to more easily specify the isolation level for the transaction.
  2. Database.UseTransaction() : which allows the DbContext to use a transaction which was started outside of the Entity Framework.

Combining several operations into one transaction within the same context

Database.BeginTransaction() has two overrides – one which takes an explicit IsolationLevel and one which takes no arguments and uses the default IsolationLevel from the underlying database provider. Both overrides return a DbContextTransaction object which provides Commit() and Rollback() methods which perform commit and rollback on the underlying store transaction.

The DbContextTransaction is meant to be disposed once it has been committed or rolled back. One easy way to accomplish this is the using(…) {…} syntax which will automatically call Dispose() when the using block completes:

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.SqlClient; 
using System.Linq; 
using System.Transactions; 
 
namespace TransactionsExamples 

    class TransactionsExample 
    { 
        static void StartOwnTransactionWithinContext() 
        { 
            using (var context = new BloggingContext()) 
            { 
                using (var dbContextTransaction = context.Database.BeginTransaction()) 
                { 
                    try 
                    { 
                        context.Database.ExecuteSqlCommand( 
                            @"UPDATE Blogs SET Rating = 5" + 
                                " WHERE Name LIKE '%Entity Framework%'" 
                            ); 
 
                        var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
                        foreach (var post in query) 
                        { 
                            post.Title += "[Cool Blog]"; 
                        } 
 
                        context.SaveChanges(); 
 
                        dbContextTransaction.Commit(); 
                    } 
                    catch (Exception) 
                    { 
                        dbContextTransaction.Rollback(); 
                    } 
                } 
            } 
        } 
    } 
}

Note: Beginning a transaction requires that the underlying store connection is open. So calling Database.BeginTransaction() will open the connection  if it is not already opened. If DbContextTransaction opened the connection then it will close it when Dispose() is called.

Passing an existing transaction to the context

Sometimes you would like a transaction which is even broader in scope and which includes operations on the same database but outside of EF completely. To accomplish this you must open the connection and start the transaction yourself and then tell EF a) to use the already-opened database connection, and b) to use the existing transaction on that connection.

To do this you must define and use a constructor on your context class which inherits from one of the DbContext constructors which take i) an existing connection parameter and ii) the contextOwnsConnection boolean.

Note: The contextOwnsConnection flag must be set to false when called in this scenario. This is important as it informs Entity Framework that it should not close the connection when it is done with it (e.g. see line 4 below):

using (var conn = new SqlConnection("...")) 

    conn.Open(); 
    using (var context = new BloggingContext(conn, contextOwnsConnection: false)) 
    { 
    } 
}

Furthermore, you must start the transaction yourself (including the IsolationLevel if you want to avoid the default setting) and let the Entity Framework know that there is an existing transaction already started on the connection (see line 33 below).

Then you are free to execute database operations either directly on the SqlConnection itself, or on the DbContext. All such operations are executed within one transaction. You take responsibility for committing or rolling back the transaction and for calling Dispose() on it, as well as for closing and disposing the database connection. E.g.:

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.SqlClient; 
using System.Linq; 
sing System.Transactions; 
 
namespace TransactionsExamples 

     class TransactionsExample 
     { 
        static void UsingExternalTransaction() 
        { 
            using (var conn = new SqlConnection("...")) 
            { 
               conn.Open(); 
 
               using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot)) 
               { 
                   try 
                   { 
                       var sqlCommand = new SqlCommand(); 
                       sqlCommand.Connection = conn; 
                       sqlCommand.Transaction = sqlTxn; 
                       sqlCommand.CommandText = 
                           @"UPDATE Blogs SET Rating = 5" + 
                            " WHERE Name LIKE '%Entity Framework%'"; 
                       sqlCommand.ExecuteNonQuery(); 
 
                       using (var context =  
                         new BloggingContext(conn, contextOwnsConnection: false)) 
                        { 
                            context.Database.UseTransaction(sqlTxn); 
 
                            var query =  context.Posts.Where(p => p.Blog.Rating >= 5); 
                            foreach (var post in query) 
                            { 
                                post.Title += "[Cool Blog]"; 
                            } 
                           context.SaveChanges(); 
                        } 
 
                        sqlTxn.Commit(); 
                    } 
                    catch (Exception) 
                    { 
                        sqlTxn.Rollback(); 
                    } 
                } 
            } 
        } 
    } 
}

Notes:

  • You can pass null to Database.UseTransaction() to clear Entity Framework’s knowledge of the current transaction. Entity Framework will neither commit nor rollback the existing transaction when you do this, so use with care and only if you’re sure this is what you want to do.
  • You will see an exception from Database.UseTransaction() if you pass a transaction:
    • When the Entity Framework already has an existing transaction
    • When Entity Framework is already operating within a TransactionScope
    • Whose connection object is null (i.e. one which has no connection – usually this is a sign that that transaction has already completed)
    • Whose connection object does not match the Entity Framework’s connection.

Using transactions with other features

This section details how the above transactions interact with:

  • Connection resiliency
  • Asynchronous methods
  • TransactionScope transactions

Connection Resiliency

The new Connection Resiliency feature does not work with user-initiated transactions. For details, see Limitations with Retrying Execution Strategies.

Asynchronous Programming

The approach outlined in the previous sections needs no further options or settings to work with the asynchronous query and save methods. But be aware that, depending on what you do within the asynchronous methods, this may result in long-running transactions – which can in turn cause deadlocks or blocking which is bad for the performance of the overall application.

TransactionScope Transactions

Prior to EF6 the recommended way of providing larger scope transactions was to use a TransactionScope object:

using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.SqlClient; 
using System.Linq; 
using System.Transactions; 
 
namespace TransactionsExamples 

    class TransactionsExample 
    { 
        static void UsingTransactionScope() 
        { 
            using (var scope = new TransactionScope(TransactionScopeOption.Required)) 
            { 
                using (var conn = new SqlConnection("...")) 
                { 
                    conn.Open(); 
 
                    var sqlCommand = new SqlCommand(); 
                    sqlCommand.Connection = conn; 
                    sqlCommand.CommandText = 
                        @"UPDATE Blogs SET Rating = 5" + 
                            " WHERE Name LIKE '%Entity Framework%'"; 
                    sqlCommand.ExecuteNonQuery(); 
 
                    using (var context = 
                        new BloggingContext(conn, contextOwnsConnection: false)) 
                    { 
                        var query = context.Posts.Where(p => p.Blog.Rating > 5); 
                        foreach (var post in query) 
                        { 
                            post.Title += "[Cool Blog]"; 
                        } 
                        context.SaveChanges(); 
                    } 
                } 
 
                scope.Complete(); 
            } 
        } 
    } 
}

The SqlConnection and Entity Framework would both use the ambient TransactionScope transaction and hence be committed together.

Starting with .NET 4.5.1 TransactionScope has been updated to also work with asynchronous methods via the use of the TransactionScopeAsyncFlowOption enumeration:

using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.SqlClient; 
using System.Linq; 
using System.Transactions; 
 
namespace TransactionsExamples 

    class TransactionsExample 
    { 
        public static void AsyncTransactionScope() 
        { 
            using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) 
            { 
                using (var conn = new SqlConnection("...")) 
                { 
                    await conn.OpenAsync(); 
 
                    var sqlCommand = new SqlCommand(); 
                    sqlCommand.Connection = conn; 
                    sqlCommand.CommandText = 
                        @"UPDATE Blogs SET Rating = 5" + 
                            " WHERE Name LIKE '%Entity Framework%'"; 
                    await sqlCommand.ExecuteNonQueryAsync(); 
 
                    using (var context = new BloggingContext(conn, contextOwnsConnection: false)) 
                    { 
                        var query = context.Posts.Where(p => p.Blog.Rating > 5); 
                        foreach (var post in query) 
                        { 
                            post.Title += "[Cool Blog]"; 
                        } 
 
                        await context.SaveChangesAsync(); 
                    } 
                } 
            } 
        } 
    } 
}

There are still some limitations to the TransactionScope approach:

  • 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.

Advantages of the TransactionScope approach:

  • It will automatically upgrade a local transaction to a distributed transaction if you make more than one connection to a given database or combine a connection to one database with a connection to a different database within the same transaction (note: you must have the MSDTC service configured to allow distributed transactions for this to work).
  • Ease of coding. If you prefer the transaction to be ambient and dealt with implicitly in the background rather than explicitly under you control then the TransactionScope approach may suit you better.

In summary, with the new Database.BeginTransaction() and Database.UseTransaction() APIs above, the TransactionScope approach is no longer necessary for most users. If you do continue to use TransactionScope then be aware of the above limitations. We recommend using the approach outlined in the previous sections instead where possible.

Entityframework 事务的更多相关文章

  1. .NET WebApi 实战第五讲之EntityFramework事务

    在<.NET WebApi 实战第二讲>中我们有提到过事务的概念!任何数据库的读操作可以没有事务,但是写事件必须有事务,如果一个后端工程师在数据库写入时未添加事务,那就不是一个合格的工程师 ...

  2. EntityFramework之异步、事务及性能优化(九)

    前言 本文开始前我将循序渐进先了解下实现EF中的异步,并将重点主要是放在EF中的事务以及性能优化上,希望通过此文能够帮助到你. 异步 既然是异步我们就得知道我们知道在什么情况下需要使用异步编程,当等待 ...

  3. EntityFramework 6.x多个上下文迁移实现分布式事务

    前言 自从项目上了.NET Core平台用上了EntityFramework Core就再没碰过EntityFramework 6.x版本,目前而言EntityFramework 6.x是用的最多,无 ...

  4. 基于 EntityFramework 的数据库主从读写分离架构(2)- 改进配置和添加事务支持

        回到目录,完整代码请查看(https://github.com/cjw0511/NDF.Infrastructure)中的目录:      src\ NDF.Data.EntityFramew ...

  5. EntityFramework与TransactionScope事务和并发控制

    最近在园子里看到一篇关于TransactionScope的文章,发现事务和并发控制是新接触Entity Framework和Transaction Scope的园友们不易理解的问题,遂组织此文跟大家共 ...

  6. EntityFramework:支持同一事务提交的批量删除数据实现思路

    一切从一段代码说起... 下面一段代码是最近我在对一EF项目进行重构时发现的. protected override void DoRemove(T entity) { this.dbContext. ...

  7. entityframework分布式事务中遇到的 “与基础事务管理器的通信失败”的解决方法

    首先是ef的多数据库操作实现事务的方法 public int AddDifferenceDB(userinfo1 user1, userinfo user) { ; using (var test2D ...

  8. 框架计划随笔 三.EntityFramework在传统事务脚本模式下的使用

    某个朋友问为什么不推首页或者允许评论,我说一直没怎么写博客,也习惯了先随便乱画再开始写文档,担心公开后一些不经意的"呓语“中得出的错误的结论会给别人错误的观点,所以这个系列只是当做熟悉写博客 ...

  9. EntityFramework之事务

    一.EF事务 引用程序集 using System.Transactions; 用法 var writer = new System.IO.StringWriter(); try { using (v ...

随机推荐

  1. paip.cache 缓存架构以及性能提升总结

    paip.cache 缓存架构以及性能提升总结 1         缓存架构以及性能(贯穿读出式(LookThrough) 旁路读出式(LookAside) 写穿式(WriteThrough) 回写式 ...

  2. java基础类和对象-题

    1.创建一个三角形类,成员变量三边,方法求周长,创建类主类A来测试它. public class Sanjiaoxing { //定义属性 private int a; private int b; ...

  3. 轻松了解Spring中的控制反转和依赖注入(二)

    紧接上一篇文章<轻松了解Spring中的控制反转和依赖注入>讲解了SpringIOC和DI的基本概念,这篇文章我们模拟一下SpringIOC的工作机制,使我们更加深刻的理解其中的工作. 类 ...

  4. 使用jquery修改css中带有!important的样式属性

    当CSS中含有!important的样式属性时,普通的修改方式是会出现失败的.如下: <div class="test">使用jquery修改css中带有!import ...

  5. Android 使用Fragment界面向下跳转并一级级返回

    http://www.cnblogs.com/_ymw/p/4227862.html 1.首先贴上项目结构图: 2.先添加一个接口文件BackHandledInterface.java,定义一个set ...

  6. 【转】BitKeeper与Linux,git史前琐事

    http://www.path8.net/tn/archives/6039 维持数年的BitKeeper与Linux的关系最终还是落入了好莱坞明星婚姻式的结局.他们曾经相得益彰,最后却走到这个遗憾的地 ...

  7. 解决12306.cn网站验证码获取提示“基础连接已经关闭: 未能为 SSL/TLS 安全通道建立信任关系“的问题

    https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=sjrand&0.8967564508222368 这是图片的访问网址 大家看清楚 ...

  8. PHP 数组排序方法总结

    sort:本函数为 array 中的单元赋予新的键名.这将删除原有的键名而不仅是重新排序. rsort:本函数对数组进行逆向排序(最高到最低). 删除原有的键名而不仅是重新排序. asort:对数组进 ...

  9. 使用ClipboardUtils兼容API LEVEL 11以下实现复杂粘贴

    实现功能:复杂粘贴内容 问题描述:android.content.ClipboardManager在APILevel 11之后才可以使用,而我们目前还要兼容API Level 10(2.3.3) 解决 ...

  10. git常用命令-基本操作

    git常用命令-基本操作 1)      新增文件 新增了Test_1.java git add Test_1.java git commit –m “新增了Test_1.java” git push ...