了解Entity Framework中事务处理

from:https://www.cnblogs.com/from1991/p/5423120.htmlhtml

Entity Framework 6之前,框架自己并无提供显式的事务处理方案,在EF6中提供了事务处理的API。sql

      全部版本的EF,只要你调用SaveChanges方法进行插入、修改或删除,EF框架会自动将该操做进行事务包装。这种方法没法对事务进行显式的控制,例如新建事务等,可能会形成事务的粒度很是大,下降效率。EF不会对查询进行事务包装。数据库

     从EF6开始,默认状况下,若是每次调用Database.ExecuteSqlCommand(),若是其不在存在于任何事务中,则会将该Command包装到一个事务中。框架提供了多种重载,容许你重写这些方法,实现事务的控制。一样,执行存储过程的ObjectContext.ExecuteFunction()方法是实现了这种机制(可是ExecuteFunction不能被重写)。这两种状况下,使用的事务隔离级别均为数据库提供的默认隔离级别,SQL Server中使用的是READ COMMITED。编程

      有同窗提供了EF6以前版本的事务方案,以下:框架

复制代码
1 using (BlogDbContext context =new BlogDbContext())
 2 {
 3     using (TransactionScope transaction =new TransactionScope())
 4     {
 5         context.BlogPosts.Add(blogPost);
 6         context.SaveChanges();
 7         postBody.ID = blogPost.ID;
 8         context.EntryViewCounts.Add(
 9             new EntryViewCount() { EntryID = blogPost.ID });
10         context.PostBodys.Add(postBody);
11         context.SaveChanges();
12         //提交事务
13         transaction.Complete();
14     } 
15 }
复制代码

      其实,上面方法执行结果不会错,可是存在隐患,这样状况下,显式事务实际上是多余的。因此我对这种方案持怀疑态度(没有进行内部代码的分析,有时间了分析下,但愿你们拍砖)。异步

      官方体统的解决方案为:分布式

复制代码
1 using System.Collections.Generic; 
 2 using System.Data.Entity; 
 3 using System.Data.SqlClient; 
 4 using System.Linq; 
 5 using System.Transactions; 
 6  
 7 namespace TransactionsExamples 
 8 { 
 9     class TransactionsExample 
10     { 
11         static void UsingTransactionScope() 
12         { 
13             using (var scope = new TransactionScope(TransactionScopeOption.Required)) 
14             { 
15                 using (var conn = new SqlConnection("...")) 
16                 { 
17                     conn.Open(); 
18  
19                     var sqlCommand = new SqlCommand(); 
20                     sqlCommand.Connection = conn; 
21                     sqlCommand.CommandText = 
22                         @"UPDATE Blogs SET Rating = 5" + 
23                             " WHERE Name LIKE '%Entity Framework%'"; 
24                     sqlCommand.ExecuteNonQuery(); 
25  
26                     using (var context = 
27                         new BloggingContext(conn, contextOwnsConnection: false)) 
28                     { 
29                         var query = context.Posts.Where(p => p.Blog.Rating > 5); 
30                         foreach (var post in query) 
31                         { 
32                             post.Title += "[Cool Blog]"; 
33                         } 
34                         context.SaveChanges(); 
35                     } 
36                 } 
37  
38                 scope.Complete(); 
39             } 
40         } 
41     } 
42 }
复制代码

 

      通常状况下,用户不须要对事务进行特殊的控制,使用EF框架默认行为便可。若是要对细节进行控制,参考下面章节:post

EF6 API工做机制

EF6之前版本EF框架本身管理数据库链接,若是你本身尝试打开链接可能会抛出异常(打开一个已打开的链接会抛出异常)。因为事务必须在一个打开的链接上执行,所以要合并一系列操做到一个事务中,要么使用TractionScope,要么使用ObjectContext.Connection属性直接执行EntityConnection的Open(),并BeginTransaction()。另外,若是你在数据库底层链接上执行了事务,上面API会失败。ui

注意:EF6中移除了仅接受关闭链接的限制。spa

EF6 开始提供了:

Database.BeginTransaction() : 为用户提供一种简单易用的方案,在DbContext中启动并完成一个事务 -- 合并一系列操做到该事务中。同时使用户更方便的指定事务隔离级别。

Database.UseTransaction() : 容许DbContext使用一个EF框架外的事务。

在同一DbContext中合并一系列操做到一个事务中

Database.BeginTransaction()有两个重载方法。一个方法提供一个IsolationLevel参数,另外一个无参方法使用底层数据库提供程序默认的数据库事务隔离级别。两个重载方法均返回一个DbContextTransaction对象,该对象提供Commit和Rollback方法,用于数据库底层事务的提交和回滚。

使用DbContextTransaction意味着,一旦提交或回滚事务,就要释放该对象。一种简单的方法是使用using语法,在using代码块结束时自动调用该对象的Dispose方法。

复制代码
1 using System; 
 2 using System.Collections.Generic; 
 3 using System.Data.Entity; 
 4 using System.Data.SqlClient; 
 5 using System.Linq; 
 6 using System.Transactions; 
 7  
 8 namespace TransactionsExamples 
 9 { 
10     class TransactionsExample 
11     { 
12         static void StartOwnTransactionWithinContext() 
13         { 
14             using (var context = new BloggingContext()) 
15             { 
16                 using (var dbContextTransaction = context.Database.BeginTransaction()) 
17                 { 
18                     try 
19                     { 
20                         context.Database.ExecuteSqlCommand( 
21                             @"UPDATE Blogs SET Rating = 5" + 
22                                 " WHERE Name LIKE '%Entity Framework%'" 
23                             ); 
24  
25                         var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
26                         foreach (var post in query) 
27                         { 
28                             post.Title += "[Cool Blog]"; 
29                         } 
30  
31                         context.SaveChanges(); 
32  
33                         dbContextTransaction.Commit(); 
34                     } 
35                     catch (Exception) 
36                     { 
37                         dbContextTransaction.Rollback(); 
38                     } 
39                 } 
40             } 
41         } 
42     } 
43 }
复制代码

注意:启动一个事务须要底层数据库链接已打开。所以,若是链接未打开,调用Database.BeginTransaction()会打开链接,在其Dispose时关闭链接。

传递一个现有事务到DbContext

      有时,你可能须要在同一数据库上,执行一个EF框架以外更大范围的事务,这是就须要本身打开链接并启动事务,而后通知EF框架:

1) 使用已打开的数据库链接

2) 在该链接上使用现有的事务

      要实现上面的行为,你须要使用继承自DbContext的构造方法XXXContext(conn,contextOwnsConnection),其中:

                   conn : 是一个已存在的数据库链接

                   contextOwnsConnection : 是一个布尔值,指示上下文是否本身占用数据库链接。

注意:这种状况下,contextOwnsConnection必须设置为false,由于它通知EF框架,在本身使用完链接后,不要关闭它。见下面代码:

复制代码
1 using (var conn = new SqlConnection("...")) 
2 { 
3     conn.Open(); 
4     using (var context = new BloggingContext(conn, contextOwnsConnection: false)) 
5     { 
6     } 
7 }
复制代码

      此外,你必须本身启动事务(若是你不想使用默认IsolationLevel,能够本身设置之)并让EF框架知道该链接上已经存在已启动的事务(参考下面代码的33行)。
      而后就能够直接在链接上执行数据库操做,或者在DbContext上执行,全部这些操做均在同一事务中执行,你负责提交或回滚事务,并调用DatabaseTransaction.Dispose(),最后要关闭和释放数据库链接。请参考如下代码:

复制代码
1 using System; 
 2 using System.Collections.Generic; 
 3 using System.Data.Entity; 
 4 using System.Data.SqlClient; 
 5 using System.Linq; 
 6 sing System.Transactions; 
 7  
 8 namespace TransactionsExamples 
 9 { 
10      class TransactionsExample 
11      { 
12         static void UsingExternalTransaction() 
13         { 
14             using (var conn = new SqlConnection("...")) 
15             { 
16                conn.Open(); 
17  
18                using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot)) 
19                { 
20                    try 
21                    { 
22                        var sqlCommand = new SqlCommand(); 
23                        sqlCommand.Connection = conn; 
24                        sqlCommand.Transaction = sqlTxn; 
25                        sqlCommand.CommandText = 
26                            @"UPDATE Blogs SET Rating = 5" + 
27                             " WHERE Name LIKE '%Entity Framework%'"; 
28                        sqlCommand.ExecuteNonQuery(); 
29  
30                        using (var context =  
31                          new BloggingContext(conn, contextOwnsConnection: false)) 
32                         { 
33                             context.Database.UseTransaction(sqlTxn); 
34  
35                             var query =  context.Posts.Where(p => p.Blog.Rating >= 5); 
36                             foreach (var post in query) 
37                             { 
38                                 post.Title += "[Cool Blog]"; 
39                             } 
40                            context.SaveChanges(); 
41                         } 
42  
43                         sqlTxn.Commit(); 
44                     } 
45                     catch (Exception) 
46                     { 
47                         sqlTxn.Rollback(); 
48                     } 
49                 } 
50             } 
51         } 
52     } 
53 }
复制代码

注意:

  • 你能够传递null到方法Database.UseTransaction()来清除EF框架对当前事务的记忆。若是你这样作,事务既不会提交也不会回滚。因此要谨慎使用之,除非你确实须要这样。
  • 若是EF框架已经持有一个事务,此时你传递一个事务,Database.UseTransaction()将抛出一个异常:

       ★ EF框架已经持有一个事务;

       ★ 当EF框架已经在一个TransactionScope中运行;

       ★ 其数据库链接对象为null (例如,无链接--一般这种状况表示事务已经完成);

       ★ 数据库链接对象与EF框架的数据库链接对象不匹配;

 对TransactionScope的一些补充

若是你使用.net framework 4.5.1及以上版本,可使用TransactionScope的TransactionScopeAsyncFlowOption参数提供对异步的支持:

复制代码
1 using System.Collections.Generic; 
 2 using System.Data.Entity; 
 3 using System.Data.SqlClient; 
 4 using System.Linq; 
 5 using System.Transactions; 
 6  
 7 namespace TransactionsExamples 
 8 { 
 9     class TransactionsExample 
10     { 
11         public static void AsyncTransactionScope() 
12         { 
13             using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) 
14             { 
15                 using (var conn = new SqlConnection("...")) 
16                 { 
17                     await conn.OpenAsync(); 
18  
19                     var sqlCommand = new SqlCommand(); 
20                     sqlCommand.Connection = conn; 
21                     sqlCommand.CommandText = 
22                         @"UPDATE Blogs SET Rating = 5" + 
23                             " WHERE Name LIKE '%Entity Framework%'"; 
24                     await sqlCommand.ExecuteNonQueryAsync(); 
25  
26                     using (var context = new BloggingContext(conn, contextOwnsConnection: false)) 
27                     { 
28                         var query = context.Posts.Where(p => p.Blog.Rating > 5); 
29                         foreach (var post in query) 
30                         { 
31                             post.Title += "[Cool Blog]"; 
32                         } 
33  
34                         await context.SaveChangesAsync(); 
35                     } 
36                 } 
37             } 
38         } 
39     } 
40 }
复制代码

 

目前,使用TransactionScope还有一些限制:

  • 须要.NET 4.5.1及以上版本才支持异步方法;
  • 不能适用于云方案(除非你确保只有一个链接 -- 云方案不支持分布式事务);
  • 不能和Database.UseTransaction()结合使用;
  • 若是你的DDL代码存在问题(例如数据库初始化问题)或没有经过MSDTC服务来支持分布式事务,将抛出异常;

使用TransactionScope的优势:

  • 自动将本地事务升级为分布式事务:前提是你有不止一个链接到给定数据库或要组合一个链接到另外一个数据库链接到同一事务(注意:你必须启动MSDTC服务以支持分布式事务)。
  • 易于编程。若是你更但愿淡化对事务的关注,而非显示操做事务,使用TransactionScope将是一个更合适的选择。

 

      随着EF6提供了Database.BeginTransaction()和Database.UseTransaction() 两个API,使用TransactionScope不在是必须的了。若是你依然使用TransactionScope,就必须留意上面限制。建议你尽量使用新的API,而非TransactionScope。

相关文章
相关标签/搜索