承接上篇,本文深入剖析 LazyLoad(延遲載入) 的 AOP 實現細節。透過 .NET 訊息鏈機制,在屬性存取時動態攔截並自動載入關聯資料,實現領域模型的透明持久化。
遵循統一模式:
LazyLoadableAttribute:標記實體類別,實作 IContextAttribute、IContextPropertyLazyLoadableProperty:上下文屬性,實作 IContributeObjectSink 注入攔截器LazyLoadableSink:實作 IMessageSink,核心邏輯處理器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 操作實例與連接字串,依屬性標記屬性動態建構查詢:
[SubEntityList(OperationTypes.All, "ID", "CompanyTypeId")]
public TCompanies Companies { get; set; }
SubEntityListAttribute 取得主鍵/外鍵對應ConditionReturnMessage 回傳[ReferenceEntity("ID", "ParentId")]
public TCompanyType ParentType { get; set; }
GetOperator.Get[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 會導致所有方法呼叫經過訊息鏈,有效能損耗。配合分頁查詢可緩解。此 AOP 實現將資料存取邏輯從業務代碼中完全分離,模型類保持純淨 POCO 風格。雖然基於 ContextBoundObject 的動態攔截有效能代價,但在中小型企業應用中,配合適當快取與分頁策略,開發效率提升遠大於效能損耗。未來可考慮改用編譯期靜態織入或原始碼產生器進一步優化。