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

AOP 實戰:實體集合更新 EntityListUpdatable 實作原始碼深度解析(中)

     編輯:關於C語言

背景:自動追蹤屬性變更,優化資料庫更新語句

問題:前端修改實體多個屬性後呼叫 Update,若無變更追蹤,需將所有欄位包含在 SQL UPDATE 中,造成不必要開銷。解決方案:AOP 攔截 set_ 方法,自動記錄實際變更的欄位,僅更新異動欄位。

一、EntityListUpdatableAttribute:標記可集合更新的實體


[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class EntityListUpdatableAttribute : Attribute, IContextAttribute, IContextProperty
{
    internal static string PropertyName = "EntityListUpdatable";

    public EntityListUpdatableAttribute() { }

    string IContextProperty.Name => PropertyName;
    void IContextProperty.Freeze(Context newContext) { }
    bool IContextProperty.IsNewContextOK(Context newCtx) => true;

    void IContextAttribute.GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
    {
        ctorMsg.ContextProperties.Add(new EntityListUpdatableProperty());
    }

    bool IContextAttribute.IsContextOK(Context ctx, IConstructionCallMessage ctorMsg)
    {
        return ctx.GetProperty(PropertyName) != null;
    }
}

二、EntityListUpdatableProperty:建立更新追蹤 Sink


internal class EntityListUpdatableProperty : IContextProperty, IContributeObjectSink
{
    void IContextProperty.Freeze(Context newContext) { }
    string IContextProperty.Name => EntityListUpdatableAttribute.PropertyName;
    bool IContextProperty.IsNewContextOK(Context newCtx)
    {
        return newCtx.GetProperty(EntityListUpdatableAttribute.PropertyName) is EntityListUpdatableProperty;
    }

    IMessageSink IContributeObjectSink.GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)
    {
        return new EntityListUpdatableSink(obj, nextSink);
    }
}

三、EntityListUpdatableSink:核心攔截邏輯


internal class EntityListUpdatableSink : IMessageSink
{
    private readonly IMessageSink _nextSink;
    private readonly MarshalByRefObject _target;
    private static readonly object SyncRoot = new object();

    public EntityListUpdatableSink(MarshalByRefObject target, IMessageSink nextSink)
    {
        lock (SyncRoot)
        {
            _target = target;
            _nextSink = nextSink;
        }
    }

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

    public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
    {
        IMethodReturnMessage ret;
        HandleMessage(msg, true, out ret);
        return _nextSink.AsyncProcessMessage(msg, replySink);
    }

    public IMessageSink NextSink => _nextSink;

    private void HandleMessage(IMessage msg, bool isAsync, out IMethodReturnMessage returnedMessage)
    {
        returnedMessage = null;
        if (!isAsync && msg is IMethodCallMessage mcm)
        {
            // 判斷是否為屬性 set 方法(排除內部標記屬性)
            bool isFill = (bool)_target.GetType()
                .GetProperty("InnerIsFill", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(_target, null);

            bool isProperty = !isFill && 
                ((mcm.MethodName.Length > 4 && mcm.MethodName.StartsWith("set_") 
                    && mcm.MethodName != "set_InnerIsFill" 
                    && mcm.MethodName != "set_InnerDataState"
                    && mcm.MethodName != "set_InnerData")
                 || mcm.MethodName == "FieldSetter");

            object oldValue = null, newValue = null;
            MemberInfo member = null;
            string propName = string.Empty;

            // 攔截前:取得舊值
            if (isProperty)
            {
                propName = mcm.MethodName == "FieldSetter" 
                    ? mcm.InArgs[1].ToString() 
                    : mcm.MethodName.Substring(4); // 移除 "set_"
                member = Utility.GetMember(_target.GetType(), propName);
                if (member == null) isProperty = false;
                else if (member.IsDefined(typeof(DataColumnAttribute), true))
                    oldValue = Utility.GetMemberValue(_target, member, propName);
            }

            // 呼叫下一個 Sink(最終執行真實 set 方法)
            returnedMessage = (IMethodReturnMessage)_nextSink.SyncProcessMessage(msg);

            // 攔截後:比較新值,若異動則記錄
            if (isProperty)
            {
                newValue = Utility.GetMemberValue(_target, member, propName);
                if (!Equals(oldValue, newValue))
                {
                    _target.GetType()
                        .InvokeMember("AddUpdateColumn", 
                            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
                            null, _target, new object[] { propName });
                }
            }
        }
        else
        {
            returnedMessage = (IMethodReturnMessage)_nextSink.SyncProcessMessage(msg);
        }
    }
}

四、關鍵設計細節解析

機制說明
同步/非同步分離SyncProcessMessage 完整攔截;AsyncProcessMessage 僅記錄不阻塞
內部標記過濾InnerIsFill、InnerDataState、InnerData 不納入追蹤
FieldSetter 支援允許透過通用方法設值,參數含屬性名
DataColumnAttribute 判斷僅追蹤對應資料庫欄位的屬性
反射呼叫 AddUpdateColumn非公開實例方法,記錄異動欄位名
執行緒安全建構函數鎖定靜態 SyncRoot

五、效能與適用性評估

  • 優點:零侵入業務代碼、自動追蹤、減少 SQL 欄位、支援多 AOP 共存
  • 缺點:ContextBoundObject Proxy 開銷、反射呼叫開銷、僅限同步呼叫完整攔截
  • 替代方案:原始碼產生器、IL Weaving (Fody)、變更追蹤基類 (INotifyPropertyChanged)

總結

EntityListUpdatableAttribute 標記實體,EntityListUpdatableSink 攔截 set_ 呼叫,透過前後值比較自動記錄異動欄位,最終僅更新必要資料庫欄位。此模式展示 AOP 在橫切關注點(變更追蹤)的強大表達力,但需權衡 ContextBoundObject 效能代價。

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