在Entity framework中使用事務
默認情況下,當EF調用SaveChanges()時,會把生成的所有SQL命令“包”到一個“事務(transaction)”中,只要有一個數據更新操作失敗,整個事務將回滾。
在多數情況下,如果你總在數據更新操作代碼中使用一個而不是多個DbContext對象,並且只是在最後調用一次SaveChanges(),那麽EF的默認事務處理機制己經夠用了,無需做額外的事情。
然而,如果出現以下的情形,你就必須顯式地處理事務了。
第一種情況:你需要分階段地保存數據,因而需要多次調用SaveChanges()或者執行修改數據庫的SQL命令。
請看以下示例代碼:
using (var context = new MyDbContext()) { try { Person3 p = context.People3.First(); p.Name ="newName" + (new Random().Next(1, 100)); context.SaveChanges(); context.Database.ExecuteSqlCommand("update Person3 setDescription={0} where Person3Id={1}", "DescriptionModified at " + DateTime.Now.ToShortTimeString(), p.Person3Id); p.age *= 2; context.SaveChanges(); } catch (Exception e) { Console.WriteLine(e.Message); } }
上述代碼中,調用兩次SaveChanges(),還有一次執行Update命令。
對於這種場景,你需要顯式地編寫事務代碼了(註:以下代碼適用於EF6):
using (var context = new MyDbContext()) { using (var transaction =context.Database.BeginTransaction()) { try { …… context.SaveChanges(); context.Database.ExecuteSqlCommand("……); …… context.SaveChanges(); transaction.Commit(); } catch (Exception e) { Console.WriteLine(e.Message); transaction.Rollback(); } } }
特別要註意一定要調用commit(),我測試發現,只要不Commit,即使沒有異常發生,事務仍將回滾,數據庫中的數據不會更新。
第二種情況,你需要使用多個DbContext保存數據,這種情況使用TransactionScope來提交事務
TransactionScope位於using System.Transactions;命名空間下,需要在引用中手動加入。該類不能被繼承。
// // 摘要: // 使代碼塊成為事務性代碼。此類不能被繼承。 public sealed class TransactionScope : IDisposable
常用參數
- TransactionScopeOption
// // 摘要: // 該範圍需要一個事務。如果已經存在環境事務,則使用該環境事務。否則,在進入範圍之前創建新的事務。這是默認值。 Required = 0, // // 摘要: // 總是為該範圍創建新事務。 RequiresNew = 1, // // 摘要: // 環境事務上下文在創建範圍時被取消。範圍中的所有操作都在無環境事務上下文的情況下完成。 Suppress = 2
- TimeSpan設置超時時間
// // 摘要: // 以指定的超時時間值和要求初始化 System.Transactions.TransactionScope 類的新實例。 // // 參數: // scopeOption: // System.Transactions.TransactionScopeOption 枚舉的一個實例,描述與此事務範圍關聯的事務要求。 // // scopeTimeout: // 在 System.TimeSpan 之後,事務範圍將超時並中止此事務。 public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout);
優點:
- 使用起來比較方便.TransactionScope可以實現隱式的事務,使你可以在寫數據訪問層代碼的時候不用考慮到事務,而在業務層的控制事務.
- 可以實現分布式事務,比如跨庫或MSMQ.
- 在EntityFramework中可以解決DbContextTransaction的多個上下文出現死鎖問題。也就是說在EF中使用TransactionScope事務時,不用考慮數據庫操作的多上下文問題。
缺點:
- 此種方式對於數據庫鎖定表,會影響其他進程的查詢,也就是對於鎖定的表,查詢也鎖定,不允許出現臟讀,其他的查詢需要掛起等待。重點是不能進行配置修改。
- 此種方式對於同一個範圍/同一個邏輯操作,需要進行多線程的鎖定處理,不然多個線程開啟同一個事務會拋出異常。
- 多個事務操作中,存在鎖定同一個表的時候也會出現死鎖現象
使用實例如下:
Test1 _context = new Test1(); Test1 _context2 = new Test1(); using (TransactionScope tran = new TransactionScope()) { try { //1.修改省 Area province = _context.Areas.FirstOrDefault(q => q.AreaLevel == 1); province.AreaName = province.AreaName + "1"; _context.SaveChanges(); Console.WriteLine(_context2.Areas.FirstOrDefault(q => q.AreaLevel == 1).AreaName); //2.修改市 Area city = _context2.Areas.FirstOrDefault(q => q.AreaLevel == 2); city.AreaName = city.AreaName + "1"; _context2.SaveChanges(); //拋出異常 throw new Exception("測試事務異常"); tran.Complete(); } catch (Exception ex) { Console.WriteLine("執行出錯:" + ex.Message); } }
特別說明:
如果你的上下文(DbContext)在程序中只有一個,那麽如果執行事務期間出現異常造成事務回滾,會出現數據模型和數據庫中數據不對應情況,也就是說數據庫的操作雖然被回滾了,但是EF中每個數據模型的狀態卻變成了事務提交後的了,但是實際上事務被回滾了。針對這個問題目前官方給的建議是,EF中如果事務回滾,就拋棄老的上下文(DbContext),使用一個新的上下文(DbContext)。
參考文獻:
EntiryFramework中事務操作(二)TransactionScope
EntityFramework 事務處理
在Entity framework中使用事務