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

AOP 實戰應用(下):以攔截器實現延遲載入

     編輯:關於C#

承接上篇,本文深入剖析 LazyLoad(延遲載入) 的 AOP 實現細節。透過 .NET 訊息鏈機制,在屬性存取時動態攔截並自動載入關聯資料,實現領域模型的透明持久化。

核心架構:Attribute → Property → Sink

遵循統一模式:

  1. LazyLoadableAttribute:標記實體類別,實作 IContextAttribute、IContextProperty
  2. LazyLoadableProperty:上下文屬性,實作 IContributeObjectSink 注入攔截器
  3. LazyLoadableSink:實作 IMessageSink,核心邏輯處理器

LazyLoadableSink 核心邏輯解析

internal class LazyLoadableSink : IMessageSink
{
    private IMessageSink _nextSink;
    private MarshalByRefObject _target;

    public IMessage SyncProcessMessage(IMessage msg)
    {
        IMethodReturnMessage returnedMsg;
        HandleMessage(msg, false, out returnedMsg);
        return returnedMsg;
    }

    private void HandleMessage(IMessage msg, bool isAsync, out IMethodReturnMessage returnedMsg)
    {
        returnedMsg = null;
        if (isAsync) return; // 僅處理同步呼叫

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

            // 判斷是否為屬性 Getter、FieldGetter 或 GetValue 呼叫
            bool isProperty = !isFill && (
                (mcm.MethodName.StartsWith("get_") && !ExcludedGetters.Contains(mcm.MethodName)) ||
                mcm.MethodName == "FieldGetter" || mcm.MethodName == "GetValue"
            );

            if (isProperty)
            {
                string propName = ResolvePropertyName(mcm);
                MemberInfo mInfo = Utility.GetMember(entityType, propName);

                // 僅處理標記了關聯屬性的成員
                if (mInfo != null && HasAssociationAttribute(mInfo))
                {
                    object oldValue = Utility.GetMemberValue(_target, mInfo, propName);
                    if (oldValue == null) // 僅在值為 null 時觸發載入
                    {
                        returnedMsg = LoadAssociationData(entityType, propName, mInfo, mcm);
                    }
                }
            }
        }

        // 未攔截或載入失敗,傳遞給下一個 Sink
        if (returnedMsg == null)
            returnedMsg = (IMethodReturnMessage)_nextSink.SyncProcessMessage(msg);
    }
}

三種關聯類型的自動載入策略

從快取取得 DataHelper 操作實例與連接字串,依屬性標記屬性動態建構查詢:

1. 子實體集

[SubEntityList(OperationTypes.All, "ID", "CompanyTypeId")]
public TCompanies Companies { get; set; }
  • 讀取 SubEntityListAttribute 取得主鍵/外鍵對應
  • 以主實體主鍵值為條件查詢關聯集合
  • 支援額外篩選條件 Condition
  • 查詢結果直接注入屬性並構造 ReturnMessage 回傳

2. 參考實體

[ReferenceEntity("ID", "ParentId")]
public TCompanyType ParentType { get; set; }
  • 以外鍵值查詢參考實體主鍵
  • 單筆查詢 GetOperator.Get

3. 參考屬性

[ReferenceProperty("ID", "CompanyId", "Name")]
public string CompanyName { get; set; }
  • 查詢參考實體後,進一步取得指定屬性值
  • 直接注入至目標屬性

模型類應用範例

[Serializable]
[DataTable("TCompanyType")]
[LazyLoadable]          // 啟用延遲載入
[EntityListUpdatable]   // 啟用實體集更新
public class TCompanyType : DataEntity
{
    [DataColumn][PrimaryKey(true)] public int Id;
    [DataColumn] public string Name;

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

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

前端調用零感知

// 直接巡覽導覽屬性,框架自動觸發延遲載入
foreach (var type in da.Select("ParentId=0"))
{
    ShowMessage("類別: " + type.Name);
    ListSubCompany(type.Companies);      // 首次存取觸發載入
    ListSubType(type.SubCompanyTypes);   // 首次存取觸發載入
}

效能考量與限制

  • ContextBoundObject 代價:繼承 ContextBoundObject 會導致所有方法呼叫經過訊息鏈,有效能損耗。配合分頁查詢可緩解。
  • Field 不支援:目前機制僅攔截屬性與特定方法,公開欄位無法實現延遲載入。
  • 遞迴風險:雙向關聯需謹慎設計,避免循環載入導致堆疊溢位。

總結

此 AOP 實現將資料存取邏輯從業務代碼中完全分離,模型類保持純淨 POCO 風格。雖然基於 ContextBoundObject 的動態攔截有效能代價,但在中小型企業應用中,配合適當快取與分頁策略,開發效率提升遠大於效能損耗。未來可考慮改用編譯期靜態織入或原始碼產生器進一步優化。

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