在 .NET 1.x 時代,開發人員主要透過 ADO.NET 針對單一資料庫連線實作本地事務。隨著 .NET 2.0 的釋出,微軟引入了全新的事務程式設計模型——System.Transactions。所有相關的類型與組件均定義於 System.Transactions 命名空間,這套模型支援顯式(使用 Transaction 類別)與隱式(使用 TransactionScope)兩種事務編程方式。本文將分上、下兩篇,詳細剖析其核心架構與實作細節。
在 System.Transactions 體系中,事務本身由 System.Transactions.Transaction 類別表示。該類別標記為 [Serializable] 並實作 ISerializable 介面,意味著事務物件可被序列化。這項特性對 WCF 分散式事務至關重要:事務必須能跨服務邊界傳播,而序列化正是傳播的前提。
Transaction 類別提供五個 EnlistXxx 方法,用於將資源管理器登記至目前事務:
EnlistDurable:針對持久化資源管理器(如資料庫)。EnlistVolatile:針對易失性資源管理器(如記憶體資料)。EnlistPromotableSinglePhase:針對可提升的資源管理器(如 SQL Server 2005/2008),支援事務提升機制。登記的目的是建立事務提交樹,讓根節點的事務管理器能在提交時沿樹狀結構通知所有參與者。不同的提交協定(2PC、SPC)對參與者介面有不同要求:IEnlistmentNotification 對應 2PC,ISinglePhaseNotification 對應 SPC,而 IPromotableSinglePhaseNotification 則支援提升。
Transaction.Current 靜態屬性(可讀寫)代表目前執行緒的「環境事務」,儲存於 TLS (Thread Local Storage) 中,故僅對當前執行緒有效。非同步呼叫不會自動傳播環境事務,若需將非同步作業納入事務,需使用相依事務。許多資源管理器(如 SQL Server、Oracle、MSMQ)具備「環境事務感知」能力,會自動登記至 Transaction.Current 所指的事務。
TransactionInformation 屬性提供事務基本資訊:
CreationTime、Status(Active/Committed/Aborted/InDoubt)。LocalIdentifier:本地識別碼,格式為 LTM_GUID:序號。DistributedIdentifier:分布式事務提升後產生的全域 GUID,隨事務跨邊界傳播,常用於稽核追蹤。Transaction 類別無公用建構子,且僅提供 Rollback 方法,無 Commit。這是因為分散式事務中,只有最初發起事務的參與者才能提交,我們稱之為可提交事務;後續登記的參與者則持有相依事務。
CommittableTransaction 繼承自 Transaction 並實作 IAsyncResult,提供三個公用建構子:
<system.transactions> 組態調整)。TimeSpan timeout:指定逾時時間。TransactionOptions options:同時指定逾時與隔離等級。組態範例:
<system.transactions>
<defaultSettings timeout="00:01:00" />
<machineSettings maxTimeout="00:10:00" />
</system.transactions>
預設逾時不得超過機器允許的最大逾時。隔離等級有 7 種,預設為 Serializable(最高隔離),依序降序為 RepeatableRead、ReadCommitted、ReadUncommitted、Snapshot、Chaos、Unspecified。
支援同步 Commit() 與非同步 BeginCommit/EndCommit。建立 CommittableTransaction 後,必須手動指派給 Transaction.Current 才會成為環境事務,進而讓感知型資源管理器自動登記。
使用兩個預存程序 P_WITHDRAW(扣款)與 P_DEPOSIT(存款),搭配 DbAccessUtil 工具類呼叫。未使用事務時,轉入帳戶不存在會導致扣款成功但存款失敗,資料不一致。引入 CommittableTransaction 後,將兩步操作包裝於同一事務,失敗時自動回滾,確保 ACID 特性。
private static void Transfer(string accountFrom, string accountTo, double amount) {
Transaction originalTransaction = Transaction.Current;
CommittableTransaction transaction = new CommittableTransaction();
try {
Transaction.Current = transaction;
Withdraw(accountFrom, amount);
Deposite(accountTo, amount);
transaction.Commit();
} catch (Exception ex) {
transaction.Rollback(ex);
throw;
} finally {
Transaction.Current = originalTransaction;
transaction.Dispose();
}
}
下一篇將深入探討 DependentTransaction 與 TransactionScope。