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

Enterprise Library 實戰:Unity Interception + PIAB 實現交易 (Transaction) Call Handler

     編輯:關於.NET

背景動機:將交易邏輯從業務代碼中剝離

傳統 ADO.NET 交易代碼充滿樣板:SqlConnection、BeginTransaction、try/catch/commit/rollback/finally/close/dispose。每個資料存取方法重複複製,導致:

  • 代碼冗餘嚴重
  • 維護困難 (修改隔離級別、超時需改所有方法)
  • 業務邏輯被基礎設施代碼淹沒
  • 無法統一擴展 (如記錄交易啟動/提交/回滾審計日誌)

解決方案:Enterprise Library Unity Interception Extension + Policy Injection Application Block (PIAB),以 Attribute 宣告式 方式將交易行為注入目標方法。

架構概覽

  • EntLib 4.1/5.0 核心組件:Unity, Unity.Interception, PolicyInjection, Logging, Common
  • Call Handler:實作 ICallHandler,攔截方法調用,管理 TransactionScope 生命週期
  • HandlerAttribute:繼承 HandlerAttribute,標記在類別/方法/屬性上,負責建立 Call Handler 實例
  • PIAB:透過 PolicyInjection.Create<T>() 建立代理實例,啟用攔截管線

核心實作:TransactionCallHandler

1. 介面定義

// EntLib 內建
public interface ICallHandler
{
    int Order { get; set; }
    IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext);
}

public abstract class HandlerAttribute : Attribute
{
    public abstract ICallHandler CreateHandler(IUnityContainer container);
}

2. 交易處理邏輯

public class TransactionCallHandler : ICallHandler
{
    private TransactionScopeOption _scopeOption = TransactionScopeOption.Required;
    private TransactionOptions _transactionOptions;
    private EnterpriseServicesInteropOption _interopOption = EnterpriseServicesInteropOption.None;
    private string _functionName;
    private string _actionDescription;

    public TransactionCallHandler(string functionName, string actionDescription, TransactionOptions transactionOptions)
    {
        this._transactionOptions = transactionOptions;
        this._functionName = functionName;
        this._actionDescription = actionDescription;
    }

    public TransactionCallHandler(TransactionScopeOption scopeOption, TransactionOptions transactionOptions,
        EnterpriseServicesInteropOption interopOption)
    {
        this._scopeOption = scopeOption;
        this._transactionOptions = transactionOptions;
        this._interopOption = interopOption;
    }

    public int Order { get; set; }

    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        IMethodReturn result;
        using (TransactionScope scope = CreateTransactionScope())
        {
            // 記錄審計日誌
            Logger.Write(string.Format("Function Name: {0}, Action: {1}.", _functionName, _actionDescription));

            // 執行目標方法
            result = getNext()(input, getNext);

            if (result.Exception == null)
            {
                Logger.Write("Action Done.");
                scope.Complete(); // 提交
            }
            else
            {
                Logger.Write(result.Exception); // 記錄例外,不呼叫 Complete → 自動回滾
            }
        }
        return result;
    }

    protected virtual TransactionScope CreateTransactionScope()
    {
        if (_interopOption == EnterpriseServicesInteropOption.None)
            return new TransactionScope(_scopeOption, _transactionOptions);
        else
            return new TransactionScope(_scopeOption, _transactionOptions, _interopOption);
    }
}

關鍵點:

  • using (TransactionScope) 確保範圍結束時自動處理 (Commit 或 Rollback)
  • getNext()(input, getNext) 執行目標方法 (或管線中下一個 Handler)
  • result.Exception == null 判斷成敗,決定 scope.Complete()
  • 整合 EntLib Logger 記錄審計軌跡

3. 參數可配置的 Attribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property)]
public class TransactionCallHandlerAttribute : HandlerAttribute
{
    private TransactionScopeOption _scopeOption;
    private TimeSpan _timeout = TimeSpan.FromMinutes(1);
    private IsolationLevel _isolationLevel = IsolationLevel.ReadCommitted;
    private TransactionOptions _transactionOptions;
    private EnterpriseServicesInteropOption _interopOption;
    private string _functionName;
    private string _actionDescription;

    // 多重建構子支援不同參數組合
    public TransactionCallHandlerAttribute() { }
    public TransactionCallHandlerAttribute(TransactionScopeOption scopeOption) { this._scopeOption = scopeOption; }
    public TransactionCallHandlerAttribute(IsolationLevel isolationLevel) { this._isolationLevel = isolationLevel; }
    public TransactionCallHandlerAttribute(TransactionScopeOption scopeOption, IsolationLevel isolationLevel) { ... }
    public TransactionCallHandlerAttribute(string functionName) : this(functionName, string.Empty) { }
    public TransactionCallHandlerAttribute(string functionName, string actionDescription) { ... }

    // 屬性公開給組態/設計工具
    public string FunctionName { get; set; }
    public string ActionDescription { get; set; }
    public TransactionScopeOption ScopeOption { get; set; }
    public IsolationLevel IsolationLevel { get; set; }
    public EnterpriseServicesInteropOption InteropOption { get; set; }

    public TransactionOptions TransactionOptions
    {
        get
        {
            if (_transactionOptions == null) _transactionOptions = new TransactionOptions();
            _transactionOptions.Timeout = _timeout;
            _transactionOptions.IsolationLevel = _isolationLevel;
            return _transactionOptions;
        }
    }

    public override ICallHandler CreateHandler(IUnityContainer container)
    {
        _transactionOptions = TransactionOptions;
        if (!string.IsNullOrEmpty(this._functionName))
            return new TransactionCallHandler(_functionName, _actionDescription, _transactionOptions);
        else
            return new TransactionCallHandler(ScopeOption, TransactionOptions, InteropOption);
    }
}

參數說明:

應用實戰:資料存取層注入交易

1. 專案結構

  • CWS.Framework.TransactionCallHandler (Class Library) — 含上述 Handler/Attribute,參考 EntLib 核心 DLL
  • WebTest (Web Application) — 參考上述專案 + PolicyInjection + Unity.Interception

2. 業務類別標記 Attribute

public class TestDA : MarshalByRefObject  // 必須繼承 MarshalByRefObject 才能被 Unity 攔截
{
    [TransactionCallHandlerAttribute(FunctionName = "User", ActionDescription = "Add User Information")]
    public void AddUser(DataSetUser ds)
    {
        SqlConnection connection = new SqlConnection();
        connection.ConnectionString = ConfigurationManager.ConnectionStrings["TransactionDemoConnectionString"].ConnectionString;
        connection.Open();
        SqlCommand command = connection.CreateCommand();
        command.CommandType = CommandType.Text;

        int i = 0;
        foreach (DataSetUser.T_USERRow row in ds.T_USER.Rows)
        {
            if (i == 1) throw new Exception("throw this exception when update second record."); // 測試回滾
            command.CommandText = "insert into T_USER ([Name],[Password]) values(@p_username,@p_password)";
            command.Parameters.Clear();
            command.Parameters.Add(new SqlParameter("@p_username", row.Name));
            command.Parameters.Add(new SqlParameter("@p_password", row.Password));
            command.ExecuteNonQuery();
            i++;
        }
        // 無需手動 Commit/Rollback/Close/Dispose — 全由 Handler 管理
    }
}

3. 呼叫端:必須用 PIAB 建立代理

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        DataSetUser ds = new DataSetUser();
        ds.T_USER.Rows.Add(new { Name = "admin", Password = "password" });
        ds.T_USER.Rows.Add(new { Name = "tomery", Password = "password" });

        // 關鍵:PolicyInjection.Create 建立代理實例,啟用攔截管線
        TestDA t = PolicyInjection.Create<TestDA>();
        t.AddUser(ds);

        // 錯誤寫法:new TestDA() — 繞過攔截,交易不生效!
    }
}

為什麼不能 new? Unity Interception 基於 MarshalByRefObject 透明代理,只有透過 PolicyInjection.Create (或 Unity Container Resolve) 產生的實例才會經過攔截管線。

驗證結果

  • 拋出例外 (i==1) → 第二筆資料未寫入、第一筆回滾、日誌記錄 "Rollback"
  • 移除例外 → 兩筆資料全寫入、日誌記錄 "Commit"

擴展性展望

同樣模式可輕鬆實作:

  • Logging Call Handler:方法進出/參數/回傳值/耗時自動記錄
  • Caching Call Handler:依參數快取回傳值
  • Validation Call Handler:參數驗證失敗直接拋出
  • Retry Call Handler:暫時性失敗自動重試
  • Performance Counter Handler:自動統計 QPS/延遲/錯誤率

總結

EntLib Unity Interception + PIAB 提供了 企業級 AOP 基礎設施:

  1. 關注點分離:交易/日誌/快取/驗證 從業務代碼剝離
  2. 宣告式配置:Attribute 標記即可啟用,零侵入業務邏輯
  3. 統一擴展點:新增横切關注點只需寫新 Handler + Attribute,不動現有代碼
  4. 配置靈活:支援程式碼 Attribute、組態檔 Policy 定義、Unity Container 註冊多種方式

這正是從「複製貼上樣板代碼」走向「可維護、可擴展、可測試」架構的關鍵一步。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
參數用途預設值
ScopeOption交易範圍行為Required (加入環境交易或建新)
IsolationLevel隔離級別ReadCommitted
Timeout交易超時1 分鐘
InteropOptionCOM+ 互通模式None
FunctionName/ActionDescription審計日誌識別資訊空