我們有舊的元件(Workflow),連接DB是透過自行封裝 Ado.NET 的元件,
而到新開發的系統,則是使用 Spring.NET + NHibernate 。
當新、舊交雜在一起使用時,交易該如何控管呢?
最簡單的就是用 TransactionScope 去包起來,
但這時候 MSDTC 就跑起來了。
但是很多單位現在都不給開 MSDTC 了,
那要怎麼辦呢? 取得 DB Connection 及 Transaction 往內傳嗎?
要如何取得 Spring.NET 中的 System.Data.Common.DbTransaction 物件呢?
跟同事Harry討論後,DB Connection 及 Transaction 都由 Spring 來控制,
讓舊元件用的 Connection 也是來自於 Spring 生成的。
那要如何讓舊的元件在取 Connection 時,可以拿到 Spring 生成的 Connection 呢?
這時,我們可以使用 Thread Local Storage 來儲存 Connection 及 Transaction 。
疑~~~ 不是 Connection 就可以了嗎? 為什麼還需要 Transaction 物件呢?
因為 Connection.BeginTransaction 後會傳回一個 Transaction 物件,
而在 Command 執行時,除了需要 Connection 物件外,還需要 Transaction 物件哦!
可以參考「SqlConnection.BeginTransaction 方法 ()」及「SqlCommand.Transaction遇到Exception時,會變成null」。
因為 Connection 在 Spring.NET 中容易取,但是 DbTransaction 要如何取得呢?
在 Spring.NET 起交易後,可以取到 TransactionStatus 物件,
在它的 Transaction 屬性中,有一個非公用成員 connectionHolder ,
connectionHolder 的 Transaction 就是我們要的 DbTransaction ,如下圖,
但如果我們依上圖的Path,透過 Reflection 去找到的,跟本找不到那個 connectionHolder 。
後來跟 Harry 才發現要先將 TransactionStatus.Transaction 轉成 AdoTransactionObjectSupport 物件。
而 AdoTransactionObjectSupport 有個 public 屬性就是 ConnectionHolder ,就可以取得 ConnectionHolder.Transaction 物件。
在一開始取得 DbTransaction 的方式如下(SpringHelper 請 Mapping 到您的 Spring.NET 物件),
var transStatus = SpringHelper.InitTransactionManagr(); //取得 Spring.NET 的 TransactionStatus
try
{
using (SpringHelper.GetScope())
{
var adoTxObj = transStatus.Transaction as AdoTransactionObjectSupport;
var connHolder = adoTxObj.ConnectionHolder;
//先檢查是否已建立
LocalDataStoreSlot ldsTx = System.Threading.Thread.GetNamedDataSlot("springTx");
//將 DbTransaction 放到 Thread Local Storage 之中
System.Threading.Thread.SetData(ldsTx, connectionHolder.Transaction);
// ... 新、舊交雜在一起使用 ...
//最後沒問題就 Commit
SpringHelper.Tx.Commit(transStatus);
}
}
catch (Exception ex)
{
//有錯誤就 Rollback
SpringHelper.Tx.Rollback(transStatus);
}
而舊系統原本 Command 在執行 SQL 之中,除了給它 Connection 外, 如果 LocalDataStoreSlot 有資料的話,就 Assign 給它
var ldsTx = System.Threading.Thread.GetNamedDataSlot("springTx");
var tx = Thread.GetData(ldsTx) as SqlTransaction;
if (tx != null)
command.Transaction = tx;
這種方式有點像是 asp.net 中的 middleware 的方式,在 begin request 時,就將 Connection 及 Transaction 放到 Thread Local Storage 之中,
所以同一個 Thread 中後面執行到需要 Connection 及 Transaction 時,就從 Thread Local Storage 拿出來用就可以了。
Hi,
亂馬客Blog已移到了 「亂馬客 : Re:從零開始的軟體開發生活」
請大家繼續支持 ^_^