首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WCF 實戰系列 (13):分散式交易投票機制深度解析——OperationContext.SetTransactionComplete 與 WPF 銀行轉帳實戰

     編輯:關於.NET

前言

WCF 交易支援不止於隱性的 `TransactionAutoComplete=true`。當業務邏輯需要**條件式提交** (如:餘額充足才扣款、風控通過才下單) 時,必須掌握**顯式交易投票**機制:`OperationContext.Current.SetTransactionComplete()`。本文結合雙銀行轉帳業務 (工商銀行 ↔ 建設銀行)、WPF 豐富客戶端、PerSession 實例模式、WS-DualHttpBinding 雙工通訊,完整演示分散式交易的顯式控制流程。

一、 核心概念:交易投票與 TransactionAutoComplete

鐵律:分散式交易要求**全體參與者一致贊成 (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`。客戶端僅呼叫一個操作,簡化邏輯但增加服務端耦合。

六、 運行驗證與預期結果

  1. 啟動 ICBC Host、CCB Host、WPF Client
  2. 初始雙方餘額均為 10
  3. 選擇 ICBC,輸入 4 點擊轉帳
  4. ICBC 餘額變 6,CCB 餘額變 14
  5. 切換 CCB 查看餘額確認為 14
  6. 測試餘額不足場景:輸入 20 → 拋出異常 → 交易回滾 → 餘額不變

七、 進階主題:交易與異常處理最佳實踐

  1. 業務異常 vs 系統異常:拋出 `FaultException` 讓客戶端捕獲業務錯誤 (餘額不足);系統異常 (DB 連線失敗) 讓交易自動中止
  2. 交易超時:`TransactionScope` 預設 1 分鐘,長時間操作需調整 `TransactionScopeTimeout`
  3. 資源管理員登記:使用 `TransactionScope` 時,ADO.NET/SQL Server 自動登記;非交易性資源 (檔案、MQ) 需實作 `IEnlistmentNotification`
  4. 診斷追蹤:開啟 `system.diagnostics` 交易追蹤來源,排查分散式交易流向

結語

WCF 的 `OperationContext.Current.SetTransactionComplete()` 賦予開發者**在服務操作內部精準控制交易命運**的能力。結合 `TransactionScope` 客戶端協調、`PerSession` 狀態管理、`TransactionFlow` 跨邊界傳播,構建出符合 ACID 的分散式業務邏輯。本文的雙銀行轉帳實戰,展示了從契約設計、服務實作、宿主部署、WPF 客戶端協調的完整鏈路,是掌握 WCF 進階交易編程的最佳範本。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
模式TransactionAutoComplete投票責任適用場景
隱性提交true (預設)框架自動:無異常即投票贊成簡單 CRUD、全成功或全失敗
顯式投票false開發者手動呼叫 SetTransactionComplete()條件式提交、多步驟業務、需在方法中決定成敗