前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ORM设计思想——智能识别更新字段与日志AOP追踪记录

ORM设计思想——智能识别更新字段与日志AOP追踪记录

作者头像
code2roc
发布2023-07-19 14:29:38
2120
发布2023-07-19 14:29:38
举报
文章被收录于专栏:IT民工的代码世界

在之前c#自己封装一个轻量级ORM框架FastORM一文中已经初步完成了对数据库查询,实体类映射,泛型方法的封装,但是对于更新字段使用的还是全字段更新,也没有日志追踪功能,在本文中,将会详细叙述完善这两个功能的过程。

更新操作字段的智能识别:

之前的FastORM初始版本的强类型更新操作,是对对象的全字段更新,如果其中含有大文本存储,将会增加数据库服务器的压力,所以决定对更新操作进行优化。首先的思路是对实体对象的Set操作进行记录,例如

代码语言:javascript
复制
    public string LoginID
    {
       get;
       set
       {
           base.SetState(value);
       }
    }

但是这种操作会增加实体类的繁琐程度,所以决定在实体类的基类中进行属性Set方法的AOP拦截,使用到c#自带的ProxyAttribute和RealProxy两个类,先来看下这两个类有什么作用

ProxyAttribute这个类用来截获对象的代理,我们只要能够替换代理,就能够在对象的初始化,方法调用的过程中加入自定义的操作,重写MarshalByRefObject方法,进行代替替换

代码语言:javascript
复制
        public override MarshalByRefObject CreateInstance(Type serverType)
        {
            SetMethodAopProxy realProxy = new SetMethodAopProxy(serverType);
            return realProxy.GetTransparentProxy() as MarshalByRefObject;
        }

那我们如何生成自己的代理呢,接下来就要使用到RealProxy这个抽象类

乍一看微软的注解可能看不明白意思,我们一点点来分析,首先理解一下什么是代理,打个比方,个对象A的有一个方法C,但是不直接调用,而是通过一个类B,将A对象作为一个参数在B的构造函数中传入,并在B的同名方法C中调用对象A的方法C,并在方法前后加入自己的操作,对于对象A,只关心方法C的操作,对于对象B只关心对象A方法C前后的操作,类似于系统中AOP的日志记录功能

透明代理和代理的作用其实是一样的,但是是作为代理内部的转发,举个生活中的例子,我们使用的电脑是客户端,路由器就是代理,使用的ssr进行访问国外网站就是透明代理,你感觉不到ssr的存在,只觉得自己通过路由器就能访问Google,具体请看下面的代码

代码语言:javascript
复制
    class SetMethodAop : ProxyAttribute
    {
        public override MarshalByRefObject CreateInstance(Type serverType)
        {
            SetMethodAopProxy realProxy = new SetMethodAopProxy(serverType);
            return realProxy.GetTransparentProxy() as MarshalByRefObject;
        }
    }

其中SetMethodAop就是真实代理,但是是表现为Attribue的无入侵方式,SetMethodAopProxy就是透明代理,将代理转发到RealProxy的SetMethodAopProxy的方法

代码语言:javascript
复制
 public class SetMethodAopProxy : RealProxy
    {
        public SetMethodAopProxy(Type serverType)
            : base(serverType)
        {
        }

        public override IMessage Invoke(IMessage msg)
        {
           

        }

    }

SetMethodAop这个类继承自RealProxy这个抽象类,复写的IMessage方法就是代理过程的处理,接下来就是拦截Set方法的具体实现

代码语言:javascript
复制
            if (msg is IConstructionCallMessage) // 如果是构造函数,按原来的方式返回即可。
            {
                IConstructionCallMessage constructCallMsg = msg as IConstructionCallMessage;
                IConstructionReturnMessage constructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)msg);
                RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue);
                return constructionReturnMessage;
            }
            else if (msg is IMethodCallMessage) //如果是方法调用(属性也是方法调用的一种)
            {

                IMethodCallMessage callMsg = msg as IMethodCallMessage;
                object[] args = callMsg.Args;
                IMessage message;
                try
                {
                    //如果是set方法,且不是设置ModelState的方法,且ModelState为Modified时,记录更新字段
                    if (callMsg.MethodName.StartsWith("set_") && args.Length == 1 && !callMsg.MethodName.Contains("ModelState"))
                    {
                        object obj = GetUnwrappedServer();
                        Type type = obj.GetType();
                        PropertyInfo ModelState = type.GetProperty("ModelState");
                        if ((ModelStateEnum)ModelState.GetValue(obj)==ModelStateEnum.Modified)
                        {
                            string fieldname = callMsg.MethodName.Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries)[1];
                            foreach (FieldInfo field in type.GetFields())
                            {
                                if (field.Name == "ModifyFieldList")
                                {
                                    List<string> ModifyFieldList = field.GetValue(obj) as List<string>;
                                    if (!ModifyFieldList.Contains(fieldname))
                                    {
                                        ModifyFieldList.Add(fieldname);
                                        field.SetValue(obj, ModifyFieldList);
                                    }
                                    break;
                                }

                            }
                        }
                        
                    }
                    object o = callMsg.MethodBase.Invoke(GetUnwrappedServer(), args);
                    message = new ReturnMessage(o, args, args.Length, callMsg.LogicalCallContext, callMsg);
                }
                catch (Exception e)
                {
                    message = new ReturnMessage(e, callMsg);
                }
                return message;
            }
            return msg;

判断MethodInfo的以set_开头并且不为设置基类状态属性ModelState的方法,将修改的字段存储内部的ModifyFieldList的List中,需要注意的是Model的基类需要继承ContextBoundObject对象

ORM的SQL语句追踪:

对于SQL语句的追踪就用到了c#的AOP拦截,原来是打算也使用ProxyAttribute进行拦截,但是因为基类操作对象中存在类似于public List<T> QueryCommand<T>()的泛型方法,会导致TypeLoadExcetion,最后发现是由于微软的ContextBoundObject限制,继承类中不能存在泛型方法,只能找别的方法,这里是使用的DynamicProxy动态代理。

先总结一下动态代理的几种实现方式

1.静态代理:使用代理类进行代码插入,业务复杂后代理类会繁杂增多

2.动态代理:可以使用三方插件,或者用微软提供代理库编写,FastORM就是使用的这种方式,但是对性能有一定的损失

3.IL编织,三方插件PostSharp就是用此种方式,性能与原生调用基本没有差别,通过对编译后的文件进行操作,在运行前插入AOP代码,缺点是PostSharp收费,并且调试比较困难

接下来就介绍如何写一个动态代理类,首先看代码

代码语言:javascript
复制
class DynamicProxy<T> : RealProxy
    {
        private readonly T _decorated;
        private Predicate<MethodInfo> _filter;
        public event EventHandler<HandleArg> BeforeExecute;
        public event EventHandler<HandleArg> AfterExecute;
        public event EventHandler<HandleArg> ErrorExecuting;
        public DynamicProxy(T decorated)
            : base(typeof(T))
        {
            _decorated = decorated;
            Filter = m => true;
        }
        public Predicate<MethodInfo> Filter
        {
            get { return _filter; }
            set
            {
                if (value == null)
                    _filter = m => true;
                else
                    _filter = value;
            }
        }
        private void OnBeforeExecute(HandleArg args)
        {
            if (BeforeExecute != null)
            {
                var methodInfo = args.MethodCall.MethodBase as MethodInfo;
                if (_filter(methodInfo))
                {
                    BeforeExecute(this, args);
                   
                }
                   
            }
        }
        private void OnAfterExecute(HandleArg args)
        {
            if (AfterExecute != null)
            {
                var methodInfo = args.MethodCall.MethodBase as MethodInfo;
                if (_filter(methodInfo))
                {
                    AfterExecute(this, args);

                }
                    
            }
        }
        private void OnErrorExecuting(HandleArg args)
        {
            if (ErrorExecuting != null)
            {
                var methodInfo = args.MethodCall.MethodBase as MethodInfo;
                if (_filter(methodInfo))
                {
                    AfterExecute(this, args);

                }
                   
            }
        }
        public override IMessage Invoke(IMessage msg)
        {
            var methodCall = msg as IMethodCallMessage;
            var methodInfo = methodCall.MethodBase as MethodInfo;
            HandleArg args = new HandleArg();
            args.Obj = _decorated;
            args.MethodCall = methodCall;
            OnBeforeExecute(args);
            try
            {
                var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
                OnAfterExecute(args);
                return new ReturnMessage(
                  result, null, 0, methodCall.LogicalCallContext, methodCall);
            }
            catch (Exception e)
            {
                OnErrorExecuting(args);
                return new ReturnMessage(e, methodCall);
            }
        }

同样也是继承与RealProxy方法,在Invoke方法中进行拦截写入代码,但是这里使用了委托事件,在调用的过程中调用委托,把具体的实现交由创建生成被代理类的工厂类,不在动态代理类中进行具体AOP的操作,增加了动态代理的高复用性与灵活性,同时增加了MethodInfo的过滤器,对拦截方法进行过滤,接下来看下被代理类的生成代码

代码语言:javascript
复制
    public class CommandFactory
    {

        public static Command Create(bool IsTrace)
        {
            var repository = new Command();
            if (IsTrace)
            {
                repository.IsTrace = IsTrace;
            }
            var dynamicProxy = new DynamicProxy<Command>(repository);
            dynamicProxy.BeforeExecute += BeforeExecute;
            dynamicProxy.AfterExecute += AfterExecute;
            dynamicProxy.ErrorExecuting += ErrorExecuting;
            //只监听TraceMethod方法
            dynamicProxy.Filter = m => m.GetCustomAttributes(typeof(TraceMethod),false).Count()>0;
            return dynamicProxy.GetTransparentProxy() as Command;
        }

        public static void BeforeExecute(object sender, HandleArg e)
        {

        }
        public static void AfterExecute(object sender, HandleArg e)
        {
            object obj = e.Obj;
            Type type = obj.GetType();
            bool IsTrace = Convert.ToBoolean(type.GetProperty("IsTrace").GetValue(obj));
            if (IsTrace)
            {
                //调用对象的Trace方法
                MethodInfo method = type.GetMethod("Trace", new Type[] { });
                object[] parameters = null;
                method.Invoke(obj, parameters);
            }
          
        }
        public static void ErrorExecuting(object sender, HandleArg e)
        {

        }

我这里仅使用了AfterExecute委托,调用被代理类的Trace方法追踪SQL语句,这里为什么不直接加入对应的日志记录操作呢,因为获取内部对象信息也需要使用多次反射,而调用方法只需要一次,提高程序的性能,而且可以将Trace方法写入接口作为标准,更利于使用动态代理对象的集中管理,需要注意的是,被代理类需要继承MarshalByRefObject类。

项目地址:FastORM: 基于ado.net封装的轻量ORM框架

参考资料:面向方面的编程 - 使用 RealProxy 类进行面向方面的编程 | Microsoft Docs

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-11-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 更新操作字段的智能识别:
相关产品与服务
智能识别
腾讯云智能识别(Intelligent Identification,II)基于腾讯各实验室最新研究成果,为您提供视频内容的全方位识别,支持识别视频内的人物、语音、文字以及帧标签,对视频进行多维度结构化分析。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档