預設情況下,當EF調用SaveChanges()時,會把生成的所有SQL命令“包”到一個“事務(transaction)”中,只要有一個數據更新操作失敗,整個事務將回滾。 在多數情況下,如果你總在數據更新操作代碼中使用一個而不是多個DbContext對象,並且只是在最後調用一次SaveChanges ...
預設情況下,當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,即使沒有異常發生,事務仍將回滾,資料庫中的數據不會更新。
第2種情況,你需要使用多個DbContext保存數據。
以下是處理這種場景的典型代碼:
static void TestTransactionScope2() { using (TransactionScope scope = new TransactionScope()) { String connStr = ……; using (var conn = newSqlConnection(connStr)) { try { conn.Open(); using (var context1 =new MyDbContext(conn, contextOwnsConnection: false)) { …… context1.SaveChanges(); } using (var context2 =new MyDbContext(conn, contextOwnsConnection: false)) { context2.Database.ExecuteSqlCommand(……); context2.SaveChanges(); } using (var context3 =new MyDbContext2(conn, contextOwnsConnection: false)) { …… context3.SaveChanges(); } scope.Complete(); } catch (Exception e) { Console.WriteLine(e.ToString()); } finally { conn.Close(); } } } }
上述代碼中有幾個關鍵點:
(1)在構造DbContext對象時,需要把一個己打開的資料庫連接對象傳給它,並且需要指定EF在DbContext對象銷毀時不關閉資料庫連接。
為實現此目的,你的DbContext對象應該類似於是這樣的,提供兩個重載的構造函數:
public class MyDbContext2 : DbContext { public MyDbContext2(DbConnection conn, boolcontextOwnsConnection):base(conn,contextOwnsConnection) { } public MyDbContext2():base() { } public DbSet<OtherEntity> OtherEntities { get; set; } …… }
註意在代碼結束時關閉連接。
(2)如果不Commit,則所有數據將不會保存。
(3)你的電腦需要啟動MSDTC(分散式交易協調器),請先在控制面板中打開Distributed Transaction Coordinator服務,否則上述代碼將在運行時拋出MSDTC服務不可用的異常。
很明顯,當事務需要使用多個不同類型的DbContext對象時,Windows需要啟動MSDTC,這會對性能有所影響,因此在開發中應該儘量避免這種情況,如無必要,不要在單個事務中使用多個不同種類的DbContext對象。
轉自:http://blog.csdn.net/bitfan/article/details/14231561