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

C# 委派實作 AOP 交易控制:以委派取代屬性的輕量化方案

     編輯:關於C#

前言

上一篇介紹了以 ContextAttribute 實作 AOP 交易,雖功能完整但涉及大量反射與物件建立,效能較差。本文探討另一種思路:將交易控制權交給呼叫端(Client),透過委派傳遞業務邏輯,由基底類別統一管理交易範圍。此法不需繼承 ContextBoundObject,無反射開銷,程式碼極簡。

核心概念對比

維度傳統 AOP (ContextAttribute)委派模式 (本文)
控制權所在伺服端(被呼叫者)客戶端(呼叫者)
基底類別需求繼承 ContextBoundObject普通類別即可
效能反射 + 訊息接收器鏈,較慢直接委派呼叫,極快
錯誤處理難以在攔截器中捕獲並處理try-catch 自然包裹
委派定義維護不需需為每種簽章定義委派

基底資料存取類別:SqlDAL

public abstract class SqlDAL
{
    #region 交易與連線字串
    private SqlTransaction _sqlTrans;
    private SqlConnectionStringBuilder _connStrBuilder;

    /// 唯讀屬性:取得目前交易(若有)
    public SqlTransaction SqlTrans
    {
        get
        {
            if (_sqlTrans == null)
            {
                // 若需支援巢狀呼叫,可從 CallContext 取得
                // object obj = CallContext.GetData("TxContext");
                // if (obj is SqlTransaction) _sqlTrans = (SqlTransaction)obj;
            }
            return _sqlTrans;
        }
        set { _sqlTrans = value; }
    }

    public virtual SqlConnectionStringBuilder ConnectionString
    {
        get
        {
            if (_connStrBuilder == null || string.IsNullOrEmpty(_connStrBuilder.ConnectionString))
                _connStrBuilder = new SqlConnectionStringBuilder(Config.SQL_SERVER_CONN_STR);
            return _connStrBuilder;
        }
        set { _connStrBuilder = value; }
    }
    #endregion

    #region ExecuteNonQuery 多載(支援交易與非交易雙模式)
    public int ExecuteNonQuery(string cmdText)
    {
        return SqlTrans == null
            ? SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, CommandType.Text, cmdText)
            : SqlHelper.ExecuteNonQuery(SqlTrans, CommandType.Text, cmdText);
    }

    public int ExecuteNonQuery(string cmdText, CommandType type)
    {
        return SqlTrans == null
            ? SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText)
            : SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText);
    }

    public int ExecuteNonQuery(string cmdText, CommandType type, params SqlParameter[] parameters)
    {
        return SqlTrans == null
            ? SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText, parameters)
            : SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText, parameters);
    }
    #endregion

    /// 
    /// 交易執行包裹器:建立連線→開啟交易→呼叫委派→提交/回滾→釋放資源
    /// 
    /// 業務邏輯委派
    /// 委派參數陣列
    public void TransactionAction(Delegate delegateMethod, params object[] args)
    {
        using (SqlConnection conn = new SqlConnection(ConnectionString.ConnectionString))
        {
            conn.Open();
            _sqlTrans = conn.BeginTransaction();
            try
            {
                // 動態呼叫委派(支援任意簽章)
                delegateMethod.DynamicInvoke(args);
                _sqlTrans.Commit();
            }
            catch (SqlException)
            {
                _sqlTrans.Rollback();
                // 記錄日誌...
                throw; // 重拋供上層處理
            }
            finally
            {
                _sqlTrans?.Dispose();
                _sqlTrans = null;
            }
        }
    }
}

業務邏輯類別:UserInfoAction

public class UserInfoAction : SqlDAL
{
    // 定義符合業務方法簽章的委派型別
    public delegate void VoidDictionaryMethod(Dictionary<string, object> dict);

    /// 純業務邏輯:新增使用者
    public void Add(Dictionary<string, object> dict)
    {
        StringBuilder sql = new StringBuilder();
        sql.Append("INSERT INTO [UserInfo] (Username, Password, Email) VALUES (@u, @p, @e)");
        // 實際應使用參數化查詢,此處略
        ExecuteNonQuery(sql.ToString());
    }

    /// 若業務「必須」在交易中執行,可內建包裹
    public void AddWithTransaction(Dictionary<string, object> dict)
    {
        TransactionAction(new VoidDictionaryMethod(_AddInternal), dict);
    }

    private void _AddInternal(Dictionary<string, object> dict)
    {
        // 實際邏輯...
        Add(dict);
    }
}

呼叫端使用範例

static void Main(string[] args)
{
    var dict = new Dictionary<string, object>();
    dict.Add("Username", "abc");
    dict.Add("Password", "abc");
    dict.Add("Email", "over140@gmail.com");

    var action = new UserInfoAction();

    // ── 方式一:普通呼叫(無交易)──
    // action.Add(dict);

    // ── 方式二:由呼叫端決定加入交易 ──
    action.TransactionAction(new UserInfoAction.VoidDictionaryMethod(action.Add), dict);

    // ── 方式三:業務強制交易(呼叫端無感)──
    // action.AddWithTransaction(dict);
}

優缺點深度分析

優點

  1. 效能極佳:無反射、無 Context 建立、無訊息接收器鏈,純委派呼叫
  2. 錯誤處理自然:try-catch 直接包裹業務邏輯,可自訂重試、記錄、轉換例外
  3. 非侵入式:業務方法完全不知交易存在,單元測試極易撰寫
  4. 彈性控制:同一方法可視情境選擇「有交易」或「無交易」執行

缺點與對策

問題說明緩解方案
委派型別爆炸每種方法簽章需對應一個委派型別使用 Action<T1, T2...> / Func<TResult> 通用委派減少定義
呼叫端需知委派型別稍微增加呼叫端複雜度提供 AddWithTransaction 等內建包裝方法隱藏細節
不支援巢狀交易若 TransactionAction 內再呼叫 TransactionAction 會覆蓋 _sqlTrans引入交易計數器或改用 TransactionScope (System.Transactions)

進階改良:採用泛型委派簡化定義

// 基底類別新增通用執行方法
public void ExecuteInTransaction(Action action)
{
    using (var conn = new SqlConnection(ConnectionString.ConnectionString))
    {
        conn.Open();
        using (var trans = conn.BeginTransaction())
        {
            try
            {
                _sqlTrans = trans;
                action(); // 呼叫無參數委派
                trans.Commit();
            }
            catch
            {
                trans.Rollback();
                throw;
            }
            finally { _sqlTrans = null; }
        }
    }
}

// 業務類別
public void Add(Dictionary<string, object> dict) { ... }

// 呼叫端
action.ExecuteInTransaction(() => action.Add(dict)); // Lambda 隱含建立 Action 委派

結語

委派模式雖需定義委派型別,但以極低複雜度換取高效能、易測試、彈性強的交易控制,適合中小型專案或效能敏感場景。若專案已引入 System.Transactions 或 ORM 框架(EF/Dapper),建議優先使用框架內建交易機制。本文旨在展示「以委派實作 AOP」的另一種思維路徑,拋磚引玉。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved