.net数据持久化封装 -如何通过修改IL代码实现
2015-11-13 11:09
781 查看
最近在做数据库持久化层的封装的一些想法在这里记录下。
我们一般情况下保存或者更更新数据时会用到两个方法分别是Create和Update
假设如果我们需要将两个方法合并成一个Save方法,我们需要如何做?说到这里或许很多童鞋都会第一时间想到反射,在做Update的时候将实体类反射生成数据库操作不就成了嘛。
反射代码:
但是问题来了,假设有些值我就需要赋值成0,或者Null怎么办呢?那我们可以在优化一下使用一个辅助属性来存储我们Set过的属性,然后在Update的时候遍历辅助属性来生成数据库操作
这里要注意一个问题那就是在封装获取数据的方法时要设置_isUpdateColumns = false 不然在从数据库获取数据时也同样会对实体类的辅助属性赋值这样就是失去了辅助属性的意义。
这样是不是就完美的解决了Save方法封装呢?我的答案肯定是No,写一个实体类让我写这么多代码我可不干,因为我很懒。
可不可以通过这种方式解决问题呢?
答案肯定是Yes。
这种处理方式需要用到对IL代码进行注入,还需要用到MSBuild Task等技术,简单的说就算是在Model工程编译后调用IL注入的程序在动态修改Get和Set方法,听起来是不是很酷的一件事情。
我们开始吧。
首先我们需要使用一个Mono.Cecil.dll(可以自己通过NuGet下载)。。。。至于在这个dll是个啥东西自己度娘吧。
接下来就是大量的IL语法,考验童鞋们自学能力的时刻到了。
我们要先
执行完毕程序用Reflector打开DLL是这个样子。
接下来就是如何在项目编译后调用这个控制台程序了。
先写到这里吧,有兴起可以看看MSBuild Task。
Demo地址:http://yunpan.cn/cLakEY5NtUyvs (提取码:71b6)
我们一般情况下保存或者更更新数据时会用到两个方法分别是Create和Update
假设如果我们需要将两个方法合并成一个Save方法,我们需要如何做?说到这里或许很多童鞋都会第一时间想到反射,在做Update的时候将实体类反射生成数据库操作不就成了嘛。
反射代码:
/// <summary> /// 更新一条数据 /// </summary> /// <param name="entity"></param> /// <returns></returns> public virtual T Update(T entity) { var filter = Builders<T>.Filter.Eq(n => n._id, entity._id); //获取属性 var propertys = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public); //获取父类属性 var basePropertys = typeof(T).BaseType.GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (var item in propertys) { //先排除父类属性 var baseProperty = basePropertys.FirstOrDefault(n => n.Name == item.Name); if (baseProperty == null)//更新集中不能有实体键_id { var property = propertys.FirstOrDefault(n => n.Name == item.Name); if (property != null) { builder = builder.Set(property.Name, property.GetValue(entity)); } } } Collection.UpdateOneAsync(filter, builder).Wait(); return entity; }
但是问题来了,假设有些值我就需要赋值成0,或者Null怎么办呢?那我们可以在优化一下使用一个辅助属性来存储我们Set过的属性,然后在Update的时候遍历辅助属性来生成数据库操作
internal protected Dictionary<string, object> _updateColumns = null; internal bool _isUpdateColumns = false; internal protected MongoEntityBase() { _updateColumns = new Dictionary<string, object>(); } internal protected void AddColumnUpdated(string columnName, object value) { if (_updateColumns != null && !_updateColumns.ContainsKey(columnName) && _isUpdateColumns) { _updateColumns.Add(columnName, value); } } public Dictionary<string, object> GetColumnUpdated() { return _updateColumns; }
private string _userId; public string UserId { get { return _userId; } set { if (value != this._userId) { this._userId = value; base.AddColumnUpdated("UserId", value); } } }
这里要注意一个问题那就是在封装获取数据的方法时要设置_isUpdateColumns = false 不然在从数据库获取数据时也同样会对实体类的辅助属性赋值这样就是失去了辅助属性的意义。
这样是不是就完美的解决了Save方法封装呢?我的答案肯定是No,写一个实体类让我写这么多代码我可不干,因为我很懒。
public string UserId { get; set; }
可不可以通过这种方式解决问题呢?
答案肯定是Yes。
这种处理方式需要用到对IL代码进行注入,还需要用到MSBuild Task等技术,简单的说就算是在Model工程编译后调用IL注入的程序在动态修改Get和Set方法,听起来是不是很酷的一件事情。
我们开始吧。
首先我们需要使用一个Mono.Cecil.dll(可以自己通过NuGet下载)。。。。至于在这个dll是个啥东西自己度娘吧。
接下来就是大量的IL语法,考验童鞋们自学能力的时刻到了。
我们要先
class Program { static void Main(string[] args) { ILModify(args[0]); } public static void ILModify(string assemblyPath) { var assembly = AssemblyDefinition.ReadAssembly(assemblyPath); List<TypeDefinition> types = new List<TypeDefinition>(); //查找ClassAttribute的属性是DbContextAttribute的类 foreach (var type in assembly.MainModule.Types) { foreach (var attribute in 4000 type.CustomAttributes) { if (attribute.AttributeType.Name.Equals("DbContextAttribute")) { types.Add(type); } } } foreach (var type in types) { #region Property //获取基类 AddColumnUpdated 方法 var addColumnUpdatedMethod = type.BaseType.Resolve().Methods.First(n => n.Name == "AddColumnUpdated"); foreach (var property in type.Properties)//获取属性集合(非变量) { Util util = new Util(property); //添加字段 var fieldNmae = "$" + property.Name; var fieldType = property.PropertyType; FieldDefinition fieldDefinition = new FieldDefinition(fieldNmae, Mono.Cecil.FieldAttributes.Private, fieldType); type.Fields.Add(fieldDefinition); //清除属性的Get方法 property.GetMethod.Body.Instructions.Clear(); var getILProcessor = property.GetMethod.Body.GetILProcessor(); getILProcessor.Append(getILProcessor.Create(OpCodes.Ldarg_0)); getILProcessor.Append(getILProcessor.Create(OpCodes.Ldfld, fieldDefinition)); getILProcessor.Append(getILProcessor.Create(OpCodes.Ret)); //清除属性的Set方法property.SetMethod.Body.Instructions.Clear(); var setILProcessor = property.SetMethod.Body.GetILProcessor(); List<Instruction> instructionList = new List<Instruction>(); setILProcessor.Append(setILProcessor.Create(OpCodes.Ret)); var variable = new VariableDefinition(property.Module.Import(typeof(bool))); property.SetMethod.Body.Variables.Add(variable); property.SetMethod.Body.InitLocals = true; instructionList.Add(setILProcessor.Create(OpCodes.Ldarg_1)); instructionList.Add(setILProcessor.Create(OpCodes.Ldarg_0)); instructionList.Add(setILProcessor.Create(OpCodes.Ldfld, fieldDefinition)); // 添加typeof(DateTime), typeof(Guid), typeof(TimeSpan), typeof(decimal), typeof(string) 类型的 != 操作符 if (util.TypeDict.ContainsKey(property.PropertyType.FullName)) { instructionList.Add(setILProcessor.Create(OpCodes.Call, util.TypeDict[property.PropertyType.FullName])); instructionList.Add(setILProcessor.Create(OpCodes.Ldc_I4_0)); } //ceq == 两个ceq就是!=(值类型) 引用类型需要添加 op_Inequality instructionList.Add(setILProcessor.Create(OpCodes.Ceq)); instructionList.Add(setILProcessor.Create(OpCodes.Ldc_I4_0)); instructionList.Add(setILProcessor.Create(OpCodes.Ceq)); instructionList.Add(setILProcessor.Create(OpCodes.Stloc_0)); instructionList.Add(setILProcessor.Create(OpCodes.Ldloc_0)); //if 为 true时 括号包含的开始位置 instructionList.Add(setILProcessor.Create(OpCodes.Brfalse_S, setILProcessor.Body.Instructions.First())); //添加赋值 this.字段 = value instructionList.Add(setILProcessor.Create(OpCodes.Ldarg_0)); instructionList.Add(setILProcessor.Create(OpCodes.Ldarg_1)); instructionList.Add(setILProcessor.Create(OpCodes.Stfld, fieldDefinition)); //添加方法 AddColumnUpdated(this.字段,value) instructionList.Add(setILProcessor.Create(OpCodes.Ldarg_0)); instructionList.Add(setILProcessor.Create(OpCodes.Ldstr, property.Name)); instructionList.Add(setILProcessor.Create(OpCodes.Ldarg_1)); instructionList.Add(setILProcessor.Create(OpCodes.Call, property.Module.Import(addColumnUpdatedMethod))); foreach (var instruction in instructionList) { setILProcessor.InsertBefore(setILProcessor.Body.Instructions.Last(), instruction); } } #endregion } if (types.Any()) { assembly.Write(assemblyPath); } } }
执行完毕程序用Reflector打开DLL是这个样子。
接下来就是如何在项目编译后调用这个控制台程序了。
先写到这里吧,有兴起可以看看MSBuild Task。
Demo地址:http://yunpan.cn/cLakEY5NtUyvs (提取码:71b6)
相关文章推荐
- c#调用COM组件
- C#实现把指定数据写入串口
- C#动态创建button的方法
- C#中抽象方法与虚拟方法的区别
- c#中虚函数的相关使用方法
- C#使用加边法计算行列式的值
- C#实现多线程的同步方法实例分析
- C#中尾递归的使用、优化及编译器优化
- C#实现子窗体与父窗体通信方法实例总结
- C#通用邮件发送类分享
- C#中this的用法集锦
- C#.NET获取拨号连接的宽带连接方法
- C#异步绑定数据实现方法
- C#实现AddRange为数组添加多个元素的方法
- C#中Equality和Identity浅析
- C#生成饼形图及添加文字说明实例代码
- C#判等对象是否相等的方法汇总
- C#简单的向量用法实例教程
- C#实现基于链表的内存记事本实例
- Exchange 2016部署实施案例篇-01.架构设计篇(上)