WCF 交易支援不止於隱性的 `TransactionAutoComplete=true`。當業務邏輯需要**條件式提交** (如:餘額充足才扣款、風控通過才下單) 時,必須掌握**顯式交易投票**機制:`OperationContext.Current.SetTransactionComplete()`。本文結合雙銀行轉帳業務 (工商銀行 ↔ 建設銀行)、WPF 豐富客戶端、PerSession 實例模式、WS-DualHttpBinding 雙工通訊,完整演示分散式交易的顯式控制流程。
| 模式 | TransactionAutoComplete | 投票責任 | 適用場景 |
| 隱性提交 | true (預設) | 框架自動:無異常即投票贊成 | 簡單 CRUD、全成功或全失敗 |
| 顯式投票 | false | 開發者手動呼叫 SetTransactionComplete() | 條件式提交、多步驟業務、需在方法中決定成敗 |
鐵律:分散式交易要求**全體參與者一致贊成 (Unanimous)** 才能提交,任一方投票反對或超時/異常 → 整體回滾。
二、 服務契約設計:雙工、巡回、交易流動
[ServiceContract(SessionMode = SessionMode.Required,
CallbackContract = typeof(IBankCallback))]
public interface IBank
{
// 接收匯款:增加餘額
[OperationContract(IsTerminating = false)]
[TransactionFlow(TransactionFlowOption.Allowed)]
decimal Receive(decimal money);
// 發送匯款:扣減餘額
[OperationContract(IsTerminating = false)]
[TransactionFlow(TransactionFlowOption.Allowed)]
decimal Send(decimal money);
// 查詢餘額
[OperationContract(IsTerminating = false)]
decimal GetBalance();
// 伺服器端協調轉帳 (替代方案)
[OperationContract(IsTerminating = false)]
decimal SendOnServer(decimal money, string toBank);
}
SessionMode.Required:配合 `InstanceContextMode.PerSession` 維持交易上下文
TransactionFlowOption.Allowed:允許客戶端交易流入服務
IsTerminating=false:操作不結束 Session
三、 服務實作:顯式投票邏輯
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.PerSession,
TransactionAutoComplete = false, // 關鍵:關閉自動提交
TransactionIsolationLevel = IsolationLevel.ReadCommitted)]
public class Bank : IBank
{
// 簡化:靜態變數模擬帳戶餘額,實際應用請使用資料庫交易
private static decimal _icbcBalance = 10;
private static decimal _ccbBalance = 10;
public decimal Receive(decimal money)
{
// 業務邏輯:收款無條件成功
if (OperationContext.Current.Channel.LocalAddress.Uri.ToString().Contains("ICBC"))
_icbcBalance += money;
else
_ccbBalance += money;
// 顯式投票贊成
OperationContext.Current.SetTransactionComplete();
return GetCurrentBalance();
}
public decimal Send(decimal money)
{
var balance = GetCurrentBalance();
if (balance < money)
throw new InvalidOperationException("餘額不足"); // 拋出異常 → 交易中止
// 扣款
if (OperationContext.Current.Channel.LocalAddress.Uri.ToString().Contains("ICBC"))
_icbcBalance -= money;
else
_ccbBalance -= money;
// 顯式投票贊成
OperationContext.Current.SetTransactionComplete();
return GetCurrentBalance();
}
public decimal GetBalance() => GetCurrentBalance();
public decimal SendOnServer(decimal money, string toBank)
{
// 服務端協調:在此建立 TransactionScope
using (var tx = new TransactionScope())
{
Send(money); // 扣款 (含投票)
// 呼叫遠端銀行 Receive (需雙工回調或另一端點)
// 略...
tx.Complete(); // 整體完成
}
return GetCurrentBalance();
}
private decimal GetCurrentBalance() =>
OperationContext.Current.Channel.LocalAddress.Uri.ToString().Contains("ICBC") ? _icbcBalance : _ccbBalance;
}
關鍵配置陷阱
- `TransactionAutoComplete=false` **強制**要求:
① `InstanceContextMode.PerSession`
② `ServiceContract.SessionMode.Required`
否則啟動時拋出異常
- 綁定必須啟用 `TransactionFlow=true` (WS-DualHttpBinding 預設支援)
四、 宿主部署:雙進程模擬兩家銀行
// ICBC Host (Port 8654)
var host = new ServiceHost(typeof(Bank), new Uri("https://127.0.0.1:8654/ICBC"));
var binding = new WSDualHttpBinding { TransactionFlow = true };
host.AddServiceEndpoint(typeof(IBank), binding, "");
host.Open();
// CCB Host (Port 8655)
var host = new ServiceHost(typeof(Bank), new Uri("https://127.0.0.1:8655/CCB"));
// 相同綁定配置...
五、 WPF 客戶端:客戶端協調交易模式
5.1 介面佈局 (XAML 精簡版)
- RadioButton 選擇銀行 (ICBC/CCB)
- 顯示即時餘額 (GetBalance 非同步輪詢)
- TextBox 輸入轉帳金額 + Button [轉帳]
5.2 轉帳按鈕核心邏輯
private void btnTransfer_Click(object sender, RoutedEventArgs e)
{
decimal money = decimal.Parse(txtAmount.Text);
var fromBank = rbICBC.IsChecked == true ? _icbcProxy : _ccbProxy;
var toBank = rbICBC.IsChecked == true ? _ccbProxy : _icbcProxy;
// 客戶端發起分散式交易範圍
using (var tx = new TransactionScope())
{
try
{
// 1. 扣款 (流入服務端交易)
decimal newBalance = fromBank.Send(money);
UpdateBalanceUI(newBalance);
// 2. 收款 (流入另一服務端交易)
toBank.Receive(money); // 不需接收回傳值
// 3. 全部成功 → 投票提交
tx.Complete();
MessageBox.Show("轉帳成功!");
}
catch (Exception ex)
{
// 任一失敗 → tx.Dispose() 時自動中止,無需 tx.Complete()
MessageBox.Show($"轉帳失敗:{ex.Message}");
}
}
}
5.3 服務端協調替代方案 (註解代碼)
若不想暴露 `Send`/`Receive` 給客戶端協調,可改用 `SendOnServer(money, toBankAddress)`,由**單一服務端**建立 `TransactionScope` 並透過雙工回調呼叫對方銀行 `Receive`。客戶端僅呼叫一個操作,簡化邏輯但增加服務端耦合。
六、 運行驗證與預期結果
- 啟動 ICBC Host、CCB Host、WPF Client
- 初始雙方餘額均為 10
- 選擇 ICBC,輸入 4 點擊轉帳
- ICBC 餘額變 6,CCB 餘額變 14
- 切換 CCB 查看餘額確認為 14
- 測試餘額不足場景:輸入 20 → 拋出異常 → 交易回滾 → 餘額不變
七、 進階主題:交易與異常處理最佳實踐
- 業務異常 vs 系統異常:拋出 `FaultException` 讓客戶端捕獲業務錯誤 (餘額不足);系統異常 (DB 連線失敗) 讓交易自動中止
- 交易超時:`TransactionScope` 預設 1 分鐘,長時間操作需調整 `TransactionScopeTimeout`
- 資源管理員登記:使用 `TransactionScope` 時,ADO.NET/SQL Server 自動登記;非交易性資源 (檔案、MQ) 需實作 `IEnlistmentNotification`
- 診斷追蹤:開啟 `system.diagnostics` 交易追蹤來源,排查分散式交易流向
結語
WCF 的 `OperationContext.Current.SetTransactionComplete()` 賦予開發者**在服務操作內部精準控制交易命運**的能力。結合 `TransactionScope` 客戶端協調、`PerSession` 狀態管理、`TransactionFlow` 跨邊界傳播,構建出符合 ACID 的分散式業務邏輯。本文的雙銀行轉帳實戰,展示了從契約設計、服務實作、宿主部署、WPF 客戶端協調的完整鏈路,是掌握 WCF 進階交易編程的最佳範本。
- 上一篇文章: WCF 實戰系列 (14):標準化 WCF 解決方案架構模板——契約、實作、宿主、代理、客戶端五層分離與多啟動調試實踐
- 下一篇文章: WCF 從理論到實踐 (12):深入掌握 WCF 事務處理
Copyright ©
程式碼之家 All Rights Reserved