本文转自:http://www.cnblogs.com/xiepeixing/p/5275999.html

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.

 
 
分类: C#,框架

[转]Working with Transactions (EF6 Onwards)的更多相关文章

  1. Working with Transactions (EF6 Onwards)

    Data Developer Center > Learn > Entity Framework > Get Started > Working with Transactio ...

  2. Testing with a mocking framework (EF6 onwards)

    When writing tests for your application it is often desirable to avoid hitting the database.  Entity ...

  3. Code-Based Configuration (EF6 onwards)

    https://msdn.microsoft.com/en-us/data/jj680699#Using

  4. Entityframework 事务

    Working with Transactions (EF6 Onwards) This document will describe using transactions in EF6 includ ...

  5. Entity Framework 项目使用心得

    在博客园很久了,一直只看不说,这是发布本人的第一个博客. 总结一下在项目中,EntityFramework使用的一下经验拿来和大家分享,希望对大家有用~ 1.         在Entity Fram ...

  6. EF Working with Transactions

    原文:https://msdn.microsoft.com/en-us/data/dn456843.aspx Prior to EF6 Entity Framework insisted on ope ...

  7. EF6 DataMigration 从入门到进阶

    引言 在EntityFramework的开发过程中我们有时因需求变化或者数据结构设计的变化经常会改动表结构.但数据库Schema发生变化时EF会要求我们做DataMigration 和UpdateDa ...

  8. Entity Framework的启动速度优化

    最近开发的服务放到IIS上寄宿之后,遇到一些现象,比如刚部署之后,第一次启动很慢:程序放置一会儿,再次请求也会比较慢.比如第一个问题,可以解释为初次请求某一个服务的时候,需要把程序集加载到内存中可能比 ...

  9. EntityFramework 如何进行异步化(关键词:async·await·SaveChangesAsync·ToListAsync)

    应用程序为什么要异步化?关于这个原因就不多说了,至于现有项目中代码异步化改进,可以参考:实际案例:在现有代码中通过async/await实现并行 这篇博文内容针对的是,EntityFramework ...

随机推荐

  1. windows下github 出现Permission denied (publickey)

    github教科书传送门:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 再学习到 ...

  2. 听justjavac大神live前端的入门与进阶小笔记

    代码规范 代码强壮,调试代码 少用变量,多用常量 少用for循环,why循环,多用函数式, 不要直接去使用框架 刷题 提高编程思维 用js去做c语音的问题 阅读别人代码,去看别人的代码 a+b> ...

  3. Find Peak Element——二分查找的变形

    A peak element is an element that is greater than its neighbors. Given an input array where num[i] ≠ ...

  4. 其实参与QtCreator开发也很容易

    http://bbs.csdn.net/topics/370241186 10个月前发过一个组建Qt团队,共同研究.学习.完善QtCreator的帖子,不过在为QtCreator提交完一个补丁后,就没 ...

  5. AndroidStudio运行项目出现DELETE_FAILED_INTERNAL_ERROR和INSTALL_CANCELED_BY_USER

    以上的错误为:无法将AS中的代码放到手机上 解决:File->Settings->Build,Execuion,Deployment->Instant Run然后把Enable In ...

  6. 第K短路模板【POJ2449 / 洛谷2483 / BZOJ1975 / HDU6181】

    1.到底如何求k短路的? 我们考虑,要求k短路,要先求出最短路/次短路/第三短路……/第(k-1)短路,然后访问到第k短路. 接下来的方法就是如此操作的. 2.f(x)的意义? 我们得到的f(x)更小 ...

  7. Hibernate 与Spring整合出现 hibernate.HibernateException: createCriteria is not valid without active transaction

    当 Hibernate 和 Spring 整合时,在 Spring 中指定的 Hibernate.cfg.xml 文件内容中要注释掉以下内容: <!-- Enable Hibernate's a ...

  8. 洛谷——P1869 愚蠢的组合数

    P1869 愚蠢的组合数 题目描述 最近老师教了狗狗怎么算组合数,狗狗又想到了一个问题... 狗狗定义C(N,K)表示从N个元素中不重复地选取K个元素的方案数. 狗狗想知道的是C(N,K)的奇偶性. ...

  9. 最长上升子序列:2016 Pacific Northwest Region Programming Contest—Division 2 Problem M

    Description A string of lowercase letters is calledalphabeticalif deleting zero or more of its lette ...

  10. [bzoj3244][noi2013]树的计数 题解

    UPD: 那位神牛的题解更新了,在这里. ------------------------------------------------------------------------------- ...