1. 程式人生 > >在Entity framework中使用事務

在Entity framework中使用事務

多線程 exe 上下文 保存數據 調用 real 默認 write sin

默認情況下,當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

常用參數

  1. TransactionScopeOption
    //
    // 摘要:
    //     該範圍需要一個事務。如果已經存在環境事務,則使用該環境事務。否則,在進入範圍之前創建新的事務。這是默認值。
    Required = 0,
    
    //
    // 摘要:
    //     總是為該範圍創建新事務。
    RequiresNew = 1,
    
    //
    // 摘要:
    //     環境事務上下文在創建範圍時被取消。範圍中的所有操作都在無環境事務上下文的情況下完成。
    Suppress = 2

  2. TimeSpan設置超時時間
            //
            // 摘要:
            //     以指定的超時時間值和要求初始化 System.Transactions.TransactionScope 類的新實例。
            //
            // 參數:
            //   scopeOption:
            //     System.Transactions.TransactionScopeOption 枚舉的一個實例,描述與此事務範圍關聯的事務要求。
            //
            //   scopeTimeout:
            //     在 System.TimeSpan 之後,事務範圍將超時並中止此事務。
            public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout);

優點:

  1. 使用起來比較方便.TransactionScope可以實現隱式的事務,使你可以在寫數據訪問層代碼的時候不用考慮到事務,而在業務層的控制事務.
  2. 可以實現分布式事務,比如跨庫或MSMQ.
  3. 在EntityFramework中可以解決DbContextTransaction的多個上下文出現死鎖問題。也就是說在EF中使用TransactionScope事務時,不用考慮數據庫操作的多上下文問題。

缺點:

  1. 此種方式對於數據庫鎖定表,會影響其他進程的查詢,也就是對於鎖定的表,查詢也鎖定,不允許出現臟讀,其他的查詢需要掛起等待。重點是不能進行配置修改。
  2. 此種方式對於同一個範圍/同一個邏輯操作,需要進行多線程的鎖定處理,不然多個線程開啟同一個事務會拋出異常。
  3. 多個事務操作中,存在鎖定同一個表的時候也會出現死鎖現象

使用實例如下:

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中使用事務