最近在做数据库持久化层的封装的一些想法在这里记录下。
我们一般情况下保存或者更更新数据时会用到两个方法分别是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 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)