瞭解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。

相關文章
相關標籤/搜索