上一篇介紹了以 ContextAttribute 實作 AOP 交易,雖功能完整但涉及大量反射與物件建立,效能較差。本文探討另一種思路:將交易控制權交給呼叫端(Client),透過委派傳遞業務邏輯,由基底類別統一管理交易範圍。此法不需繼承 ContextBoundObject,無反射開銷,程式碼極簡。
| 維度 | 傳統 AOP (ContextAttribute) | 委派模式 (本文) |
|---|---|---|
| 控制權所在 | 伺服端(被呼叫者) | 客戶端(呼叫者) |
| 基底類別需求 | 繼承 ContextBoundObject | 普通類別即可 |
| 效能 | 反射 + 訊息接收器鏈,較慢 | 直接委派呼叫,極快 |
| 錯誤處理 | 難以在攔截器中捕獲並處理 | try-catch 自然包裹 |
| 委派定義維護 | 不需 | 需為每種簽章定義委派 |
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;
}
}
}
}
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);
}
try-catch 直接包裹業務邏輯,可自訂重試、記錄、轉換例外| 問題 | 說明 | 緩解方案 |
|---|---|---|
| 委派型別爆炸 | 每種方法簽章需對應一個委派型別 | 使用 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」的另一種思維路徑,拋磚引玉。