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之前版本EF框架本身管理數據庫鏈接,若是你本身嘗試打開鏈接可能會拋出異常(打開一個已打開的鏈接會拋出異常)。因爲事務必須在一個打開的鏈接上執行,所以要合併一系列操做到一個事務中,要麼使用TractionScope,要麼使用ObjectContext.Connection屬性直接執行EntityConnection的Open(),並BeginTransaction()。另外,若是你在數據庫底層鏈接上執行了事務,上面API會失敗。ui
注意:EF6中移除了僅接受關閉鏈接的限制。spa
Database.BeginTransaction() : 爲用戶提供一種簡單易用的方案,在DbContext中啓動並完成一個事務 -- 合併一系列操做到該事務中。同時使用戶更方便的指定事務隔離級別。
Database.UseTransaction() : 容許DbContext使用一個EF框架外的事務。
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時關閉鏈接。
有時,你可能須要在同一數據庫上,執行一個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 }
注意:
★ EF框架已經持有一個事務;
★ 當EF框架已經在一個TransactionScope中運行;
★ 其數據庫鏈接對象爲null (例如,無鏈接--一般這種狀況表示事務已經完成);
★ 數據庫鏈接對象與EF框架的數據庫鏈接對象不匹配;
若是你使用.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 }
隨着EF6提供了Database.BeginTransaction()和Database.UseTransaction() 兩個API,使用TransactionScope不在是必須的了。若是你依然使用TransactionScope,就必須留意上面限制。建議你儘量使用新的API,而非TransactionScope。