2010-07-06 125 views
15

我有一個父對象(DAL的一部分),其中包含子對象的集合(List<t>)。TransactionScope:避免分佈式事務

當我將對象保存回數據庫時,我輸入/更新父對象,然後遍歷每個子對象。爲了可維護性,我將孩子的所有代碼放入一個單獨的私有方法中。我想要使​​用標準的ADO Transactions,但是在我的旅行中,我偶然發現了TransactionScope對象,我相信這將使我能夠在父方法中包含所有數據庫交互(以及子方法中的所有交互)在一次交易中。

到目前爲止這麼好..?

所以下一個問題是如何創建和使用此TransactionScope內的連接。我聽說使用多個連接,即使它們是同一個數據庫,也會強制TransactionScope認爲它是一個分佈式事務(涉及一些昂貴的DTC工作)。

是這樣嗎?或者是,因爲我似乎正在讀其他地方,使用相同的連接字符串(這將適用於連接池)將罰款?

更實際地說,我...

  1. 創建父&孩子單獨的連接(儘管有相同的連接字符串)
  2. 創建父連接的通過將它作爲參數(對我來說似乎笨拙)
  3. 做別的事...?

UPDATE:

雖然看起來我會用我平時.NET3.5 +和SQL Server 2008+即可,該項目的另一部分將使用Oracle(10克),所以我不妨嘗試一種可以在整個項目中一致使用的技術。

所以我只是簡單地將連接傳遞給子方法。


選項1個代碼示例:

using (TransactionScope ts = new TransactionScope()) 
      { 
       using (SqlConnection conn = new SqlConnection(connString)) 
       { 
        using (SqlCommand cmd = new SqlCommand()) 
        { 
         cmd.Connection = conn; 
         cmd.Connection.Open(); 
         cmd.CommandType = CommandType.StoredProcedure; 

         try 
         { 
          //create & add parameters to command 

          //save parent object to DB 
          cmd.ExecuteNonQuery(); 

          if ((int)cmd.Parameters["@Result"].Value != 0) 
          { 
           //not ok 
           //rollback transaction 
           ts.Dispose(); 
           return false; 
          } 
          else //enquiry saved OK 
          { 
           if (update) 
           { 
            enquiryID = (int)cmd.Parameters["@EnquiryID"].Value; 
           } 

           //Save Vehicles (child objects) 
           if (SaveVehiclesToEPE()) 
           { 
            ts.Complete(); 
            return true; 
           } 
           else 
           { 
            ts.Dispose(); 
            return false; 
           } 
          } 
         } 
         catch (Exception ex) 
         { 
          //log error 
          ts.Dispose(); 
          throw; 
         } 
        } 
       } 
      } 
+3

請參閱[在某些機器上TransactionScope自動升級到MSDTC?](http://stackoverflow.com/questions/1690892/transactionscope-automatically-escalating-to-msdtc-on-some-machines/1693795#1693795)。有幾個很好的答案,但我鏈接的是最簡潔的(並與您的問題相關)。結果是,如果您使用.NET 2.0和SQL Server 2005,即使使用具有相同連接字符串的兩個連接,您也會升級。這對於.NET 3.5和SQL Server 2008來說並不是問題。 – 2010-07-06 15:36:21

+0

我一般都使用.NET 3.5/4和SQL 2008,但偶爾我可能會使用SQL2005/2000,所以無論如何它值得記住。謝謝 – CJM 2010-07-06 15:44:08

+0

任何人都可以給我一些關於什麼是分佈式事務的知識。用例子來解釋。 – Thomas 2016-02-04 14:33:05

回答

24

當您使用TransactionScope跨多個連接進行事務處理時,即使它們共享相同的連接字符串,許多數據庫ADO提供程序(例如Oracle ODP.NET)確實開始分佈式事務處理。

某些提供程序(如.NET 3.5+中的SQL2008)可識別何時在引用相同連接字符串的事務作用域中創建新連接,並且不會導致DTC工作。但是,連接字符串中的任何差異(如調整參數)都可能阻止這種情況發生 - 並且行爲將恢復爲使用分佈式事務。

不幸的是,確保您的事務的唯一可靠方法將在不創建分佈式事務的情況下一起工作,即將連接對象(或IDbTransaction)傳遞給需要在同一事務中「繼續」的方法。

有時它有助於提升與正在其中工作的類的成員的連接,但這可能會造成尷尬的情況 - 並且控制連接對象的生命週期和處置變得複雜(因爲它通常會阻止使用聲明的using)。

+0

我的印象是,Connection必須在TransactionScope中創建,才能被覆蓋。我猜測,簡單地通過連接通過比找出一些其他解決方法更簡單和更整潔? – CJM 2010-07-06 15:38:45

+0

你知道這是否因Oracle 12c而改變? 我看到它標記爲「ODAC 12c或更高版本中可用」。這裏:https://apex.oracle.com/pls/apex/f?p = 18357:39:1473540763666 :: NO :: P39_ID:27121 – pauloya 2013-12-18 10:24:06

1

在您的例子中的TransactionScope仍處於一種方法的情況下,你可以簡單地創建與下面多個命令的的SqlTransaction。如果您想將事務移出方法,或者說該方法的調用方,或者您訪問多個數據庫,請使用TransactionScope。

更新:沒關係我只是發現了孩子的電話。在這種情況下,您可以將連接對象傳遞給子類。另外,您不需要手動處理TransactionScope - 使用塊就像try-finally塊一樣,即使在異常情況下也會執行處置。

更新2:更好的是,將IDbTransaction傳遞給子類。連接可以從中獲取。

+0

是的,我明白,處置並不是明確需要的,但是當我將TransactionScope改進爲我的代碼時,顯然我忘記了並繼續將我的舊trm.Rollback語句轉換爲ts.Dispose而不用考慮。好地方! – CJM 2010-07-06 15:41:18

+0

Re **更新2 ** - 您可以將'IDbConnection'傳遞給子類。如果使用'IbConnection.BeginTransaction()',所有使用'IDbConnection.CreateCommand()'創建的命令將自動關聯事務。這樣做更好,因爲它是傳遞層次結構的少一個參數,並且減少了耦合,因爲子類不需要關心它們是否在事務中執行。 – 2013-05-20 10:28:22

2

根據經驗,我確定(對於SQL Server提供者)如果該進程可以利用連接池共享父進程和子進程之間的連接(以及事務),則DTC不一定會變成參與其中。

但是,根據您的示例,父進程創建的連接不能由子進程共享(您在調用子進程之前不關閉/釋放連接),這是一個很大的「if」。這將導致跨越兩個實際連接的事務,這將導致事務被提升爲分佈式事務。

似乎很容易重構你的代碼以避免這種情況:在調用子進程之前關閉由父進程創建的連接。

+0

你是說如果我通過連接對象,我需要關閉父項並重新打開子項? – CJM 2010-07-06 16:04:14

+0

不,我假定您正在嘗試利用TransactionScope的便利性,而不會將事務提升爲分佈式事務。這聽起來像是在針對Oracle數據庫時不可能實現,但可以將SQL Server數據庫作爲目標,只要連接可以合併,並且一次只有一個打開的連接。 – 2010-07-06 17:08:30