2013-05-31 47 views
9

當我們需要做的在我們的應用程序的數據庫訪問,我們採用以下模式:的SqlConnection和避免推廣MSDTC

  • 用於查詢,我們有一個靜態工廠類的方法CreateOpenConnection這確實不外乎new SqlConnection(myConnectionString)並在其上調用Open()。在執行查詢之前調用此方法,並在查詢返回後處理連接。
  • 對於插入/更新/刪除我們使用其中的變化是成批並通過調用提交到數據庫work.Commit()像這樣工作模式的單位:

work.Commit:

using (var tranScope = new TransactionScope(TransactionScopeOption.RequiresNew)) 
{ 
    using (var conn = DapperFactory.CreateOpenConnection()) 
    { 
     var count = _changeTracker.CommitChanges(conn); 

     tranScope.Complete(); 

     return count; 
    } 
} 

這似乎很適合作爲web服務的一部分的一般用法,但是當我嘗試與Rebus結合使用時,目前給我MSDTC的麻煩。

從我所知道的情況來看,Rebus(當它處理隊列中的消息時)會創建一個新的TransactionScope,以便在消息處理失敗的情況下可以回滾。現在,這本身迄今爲止工作得很好。我可以打開一個滷麪消息處理程序中一個新的SqlConnection沒有任何問題(然而,用我們傳統的實體框架查詢相同滷麪TransactionScope不起作用裏面手動SqlConnections,但我不認爲一個問題現在) 。不過,昨天我提出以下問題:

Serial processing of a certain message type in Rebus

這個問題的答案似乎是使用滷麪的傳奇特性。我試圖實現這一點,並配置它,以便Rebus傳奇獲取持久化到一個新的SQL Server數據庫(具有不同的連接字符串)。據推測,使用SQL Server的持久性打開了自己的SqlConnection,因爲任何時候,我現在嘗試創建一個SqlConnection,我得到以下異常:對於分佈式事務管理器

網絡訪問(MSDTC)已被禁用。請使用組件服務管理工具在MSDTC的安全配置中啓用DTC以進行網絡訪問。

啓用MSDTC是我會非常,非常希望避免這樣做,關於配置和性能開銷。我可能是錯的,但它似乎也不是必要的。

我估計這裏發生的事情是,Rebus創建了一個環境TransactionScope並且它創建了該範圍的登記信息。當我嘗試創建我自己的SqlConnection時,它也嘗試登錄到該環境範圍,並且由於涉及多個連接,它將被提升爲MSDTC,從而失敗。

我對如何解決此問題的想法,但我不知道它是做正確的事。我會做的是:

  • 添加Enlist=false到我的應用程序的連接字符串,以便它永遠不會徵到環境事務。
  • 修改Commit方法,以便它不會創建一個新的TransactionScope(我的連接不會再訂閱,因爲我只是告訴它不應該),但它使用conn.BeginTransaction

像這樣:

var transaction = conn.BeginTransaction(); 

try 
{ 
    var count = _changeTracker.CommitChanges(conn); 
    transaction.Commit(); 
    return count; 
} 
catch 
{ 
    transaction.Rollback(); 
    throw; 
} 
finally 
{ 
    transaction.Dispose(); 
} 

我只是不知道這是正確的做法,什麼可能的缺點。

任何提示?

UPDATE:要澄清,這不是一個已經給我的問題了work.Commit(),我敢肯定它會工作,但我從來沒有到那裏,因爲我查詢就是失敗。

什麼失敗的例子:一個SqlConnection由滷麪打開

public int? GetWarehouseID(int appID) 
{ 
    var query = @" 
select top 1 ID from OrganizationUnits o 
where TypeID & 16 = 16 /* warehouse */"; 

    using (var conn = _dapper.OpenConnection()) 
    { 
    var id = conn.Query<int?>(query).FirstOrDefault(); 

    return id; 
    } 
} 

這被當TransactionScope已經滷麪創建調用,以及之後。打開我的SqlConnection,它試圖登記和崩潰

+0

如果您使用「Enlist = false」,肯定會使您的'TransactionScope'毫無意義?因爲連接*不會在其中* –

+0

同樣,你的'BeginTransaction'代碼*沒有使用事務* - 需要在命令中明確指定ADO.NET事務,所以你需要通過'交易'到'CommitChanges',當然? –

+0

你能說清楚你正在使用哪個版本的sql server嗎? –

回答

3

我有些驚訝,你看到這個,因爲RequiresNew應該意味着它是從其他交易隔離;通常,這個消息意味着2個連接在事務範圍內被激活 - 你是當然沒有其他代碼在該塊內創建/打開連接嗎?

您提出的解決方案應該可以工作 - 雖然在某些方面TransactionScopeOption.Suppress可能比改變你的配置更方便(但是兩者都應該工作)。然而,有一個問題:ADO.NET數據必須被傳遞給各個命令,所以你需要(也整理一下代碼):

using(var transaction = conn.BeginTransaction()) { 
    try { 
     var count = _changeTracker.CommitChanges(conn, transaction); 
     transaction.Commit(); 
     return count; 
    } catch { 
     transaction.Rollback(); 
     throw; 
    } 
} 

其中CommitChanges接受交易 - 或許使用可選參數:

int CommitChanges(DbConnection connection, DbTransaction transaction = null) 
{ ... } 

你的DapperFactory命名建議您使用「短小精悍」 - 在這種情況下,你可以傳遞到「短小精悍」無論是空或不是,即

conn.Execute(sql, args, transaction: transaction); 
+0

對不起,澄清(並查看我的更新)它不是失敗的'Commit',而是查詢。我的提交代碼只是爲了說明如果我添加'Enlist = false'並仍然保持'Commit'是原子的,我建議修改該代碼(因爲它確實不再對我創建的'TransactionScope'執行任何操作。在交易中傳遞的部分是我忽略的,謝謝提醒:) – JulianR

+0

@JulianR我不認爲'Commit' *是錯誤的;我試圖說明2點 - 首先,代碼需要通過交易 - 然後我忘了在示例中加入***(請參閱編輯); *其次,*您的'try' /'catch' /'finally'過於複雜並且可以簡化 –

+0

是的,我指的是您希望'RequiresNew'工作的評論,它可能的確如此。但假設我在'DbConnection'中做了必要的傳遞改變,你認爲我的新方法會起作用嗎?我擔心的是插入/更新/刪除不是原子,或者'enlist = false'對查詢有一些負面/奇怪的後果。從某種意義上說,如果你知道我的意思,它就像'修復'一個異常。「 – JulianR

1

這很大程度上取決於您使用的SQL Server版本。對於解決類似問題的其他問題見here

它與SQL 2005和SQL 2008在處理同一個TransactionScope中的多個連接時有何不同。即SQL 2008可以在相同的TransactionScope中打開多個連接而不升級到MSDTC。

難道這是你所看到

如果是這種情況的問題,我認爲只有兩個選擇都升級到SQL 2008或啓用MSDTC。我明白,這兩種選擇可能都是頭痛的問題。

+1

必須強調的是,2008處理仍然只適用於兩個連接的連接字符串和線程標識相同,並且連接打開時串行(不同時) –