標籤:name 官方 art 手動 .exe 不能被繼承 操作 .net 需要
預設情況下,當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命令。
如果在最後一次SaveChanges()中出現異常,雖然最後一次沒成功,但你會發現前兩次資料己經儲存!這就帶來了資料不一致的問題。
對於這種情境,你需要顯式地編寫事務代碼了(註:以下代碼適用於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中使用事務