在之前c#自己封装一个轻量级ORM框架FastORM一文中已经初步完成了对数据库查询,实体类映射,泛型方法的封装,但是对于更新字段使用的还是全字段更新,也没有日志追踪功能,在本文中,将会详细叙述完善这两个功能的过程。
更新操作字段的智能识别:
之前的FastORM初始版本的强类型更新操作,是对对象的全字段更新,如果其中含有大文本存储,将会增加数据库服务器的压力,所以决定对更新操作进行优化。首先的思路是对实体对象的Set操作进行记录,例如
public string LoginID
{
get;
set
{
base.SetState(value);
}
}
但是这种操作会增加实体类的繁琐程度,所以决定在实体类的基类中进行属性Set方法的AOP拦截,使用到c#自带的ProxyAttribute和RealProxy两个类,先来看下这两个类有什么作用
ProxyAttribute这个类用来截获对象的代理,我们只要能够替换代理,就能够在对象的初始化,方法调用的过程中加入自定义的操作,重写MarshalByRefObject方法,进行代替替换
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,具体请看下面的代码
class SetMethodAop : ProxyAttribute
{
public override MarshalByRefObject CreateInstance(Type serverType)
{
SetMethodAopProxy realProxy = new SetMethodAopProxy(serverType);
return realProxy.GetTransparentProxy() as MarshalByRefObject;
}
}
其中SetMethodAop就是真实代理,但是是表现为Attribue的无入侵方式,SetMethodAopProxy就是透明代理,将代理转发到RealProxy的SetMethodAopProxy的方法
public class SetMethodAopProxy : RealProxy
{
public SetMethodAopProxy(Type serverType)
: base(serverType)
{
}
public override IMessage Invoke(IMessage msg)
{
}
}
SetMethodAop这个类继承自RealProxy这个抽象类,复写的IMessage方法就是代理过程的处理,接下来就是拦截Set方法的具体实现
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收费,并且调试比较困难
接下来就介绍如何写一个动态代理类,首先看代码
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的过滤器,对拦截方法进行过滤,接下来看下被代理类的生成代码
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类。