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

AOP 實戰應用(上):實體集更新與延遲載入架構設計

     編輯:關於C#

在資料存取框架開發中,實體集更新追蹤與延遲載入是兩大核心需求。本文展示如何利用 .NET 訊息鏈與 Context 屬性機制,透過 AOP(面向切面程式設計)優雅地解決這兩個問題,實現領域模型與持久化邏輯的徹底解耦。

架構基石:ContextBoundObject 與訊息鏈

所有實體類別繼承 ContextBoundObject,進入 .NET 運行時的上下文邊界。自訂 IContextAttribute 與 IContextProperty 可在物件建構時注入 IContributeObjectSink,構建攔截器鏈(Sink Chain)處理方法呼叫訊息(IMessage)。

兩大 AOP 共用相同架構模式:

Attribute (IContextAttribute, IContextProperty)
    → ContextProperty (IContextProperty, IContributeObjectSink)
        → MessageSink (IMessageSink) 核心邏輯

一、實體集更新:EntityListUpdatable

問題背景

更新實體集時,若不知哪些屬性已修改,全欄位更新效能差;手動在每個 Setter 呼叫 AddUpdateColumn 侵入性強、易遺漏。

核心實現:EntityListUpdatableSink

internal class EntityListUpdatableSink : IMessageSink
{
    private void HandleMessage(IMessage msg, bool isAsync, out IMethodReturnMessage retMsg)
    {
        retMsg = null;
        if (isAsync) { retMsg = (IMethodReturnMessage)_nextSink.SyncProcessMessage(msg); return; }

        if (msg is IMethodCallMessage mcm)
        {
            bool isFill = (bool)_target.GetType().GetProperty("InnerIsFill", ...).GetValue(_target, null);

            // 僅攔截 Setter(排除內部狀態屬性)與 FieldSetter
            bool isSetter = !isFill && (
                (mcm.MethodName.StartsWith("set_") && !ExcludedSetters.Contains(mcm.MethodName)) ||
                mcm.MethodName == "FieldSetter"
            );

            if (isSetter)
            {
                string propName = mcm.MethodName == "FieldSetter" 
                    ? mcm.InArgs[1].ToString() 
                    : mcm.MethodName.Substring(4);
                MemberInfo mInfo = Utility.GetMember(_target.GetType(), propName);

                // 僅處理標記 [DataColumn] 的屬性
                if (mInfo != null && mInfo.IsDefined(typeof(DataColumnAttribute), true))
                {
                    object oldVal = Utility.GetMemberValue(_target, mInfo, propName);
                    
                    // 先執行原始 Setter
                    retMsg = (IMethodReturnMessage)_nextSink.SyncProcessMessage(msg);
                    
                    object newVal = Utility.GetMemberValue(_target, mInfo, propName);
                    if (!Equals(oldVal, newVal)) // 值真正改變
                    {
                        // 反射呼叫內部方法記錄修改欄位
                        _target.GetType().InvokeMember("AddUpdateColumn", 
                            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
                            null, _target, new object[] { propName });
                    }
                    return;
                }
            }
        }
        retMsg = (IMethodReturnMessage)_nextSink.SyncProcessMessage(msg);
    }
}

關鍵設計點

  1. 前置捕獲舊值:在傳遞訊息給下一個 Sink 前先讀取舊值
  2. 後置比對新值:原始 Setter 執行完畢後比對,確認實際變更
  3. 非侵入式:模型類無需任何額外代碼,僅需標記 [EntityListUpdatable]
  4. 相容性:支援屬性 Setter、FieldSetter、SetValue 多種賦值方式

二、延遲載入:LazyLoadable

架構對應

  • LazyLoadableAttribute:標記類別,Context 屬性名 "LazyLoadable"
  • LazyLoadableProperty:注入 LazyLoadableSink
  • LazyLoadableSink:攔截 Getter,依屬性標記動態載入關聯資料

三大關聯屬性標記

屬性適用場景關鍵參數
[SubEntityList]一對多集合OperationTypes, PrimaryKey, ForeignKey, Condition
[ReferenceEntity]多對一實體PrimaryKey, ForeignKey, ReferenceType
[ReferenceProperty]多對一單屬性PrimaryKey, ForeignKey, ReferenceType, ReferencePropertyName

快取機制支援

實體集載入時將 DataHelper 類型與連接字串快取為 InnerCache<object[]>,Key 由 DataCacheKeyManager 管理。延遲載入時從快取重建操作實例,避免重複解析配置。

模型類實際應用

[Serializable]
[DataTable("TCompanyType")]
[LazyLoadable]
[EntityListUpdatable]
public class TCompanyType : DataEntity
{
    [DataColumn][PrimaryKey(true)] public int Id;
    [DataColumn] public string Name;
    [DataColumn] public BaseType BaseType;
    [DataColumn] public int CompanyId;
    [DataColumn] public bool IsDelete;
    [DataColumn][ForeignKey(OperationTypes.Delete, typeof(TCompanyType), "Id")] 
    public int ParentId;
    [DataColumn] public int ChildCount;
    [DataColumn] public int Sort;

    [SubEntityList(OperationTypes.All, "ID", "CompanyTypeId")]
    public TCompanies Companies { get; set; }

    [SubEntityList(OperationTypes.All, "ID", "ParentId")]
    public TCompanyTypes SubCompanyTypes { get; set; }
}

前端調用體驗

var da = new ATCompanyType();
da.AccessOptions = AccessOptions.Defined;
var roots = da.Select("ParentId=0"); // 僅載入根節點

foreach (var type in roots)
{
    Console.WriteLine(type.Name);
    // 首次存取 Companies 觸發延遲載入
    foreach (var comp in type.Companies) 
        Console.WriteLine("  公司: " + comp.Name);
    // 首次存取 SubCompanyTypes 觸發遞迴載入
    PrintSubTypes(type.SubCompanyTypes);
}

總結與展望

此雙 AOP 設計實現了:

  • 修改追蹤自動化:零代碼侵入記錄髒欄位,Update 時僅產生必要 SQL
  • 關聯載入透明化:業務代碼如同操作記憶體物件圖,框架按需載入
  • 關注點分離:持久化邏輯集中於 Sink 與 Operator,模型類保持純淨

已知限制:ContextBoundObject 代理開銷、Field 不支援、深度遞迴風險。後續可探索 Roslyn Source Generator 或 IL Weaving 實現編譯期織入,消除運行時效能損耗。

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