c# 运行时生成动态代理类(Dynamic Proxy)监控 实体属性的变化
最近受博客园某篇文章的博主启发,研究了一下用c#的动态代理(Dynamic Proxy)模式监控实体属性的变更。
背景知识:用ORM实体框架怎么样去更新实体,就拿Entity Framework举个例子,EF提供了2种方式去更改
1. 先去数据库获取实体,然后在实体上进行修改,修改后调用SaveChanges,此时EF会根据你修改的属性动态生成部分字段的更新
代码如下:
using (MatureCMSDbContext dbContext = new MatureCMSDbContext()) {
Category category = dbContext.Set<Category>().FirstOrDefault(p => p.Id == 1); category.Name = "Test name"; dbContext.SaveChanges(); }
生成的SQL为:
exec sp_executesql N'update [dbo].[Categories] set [Name] = @0 where ([Id] = @1) ',N'@0 nvarchar(20),@1 bigint',@0=N'Test name',@1=1
2. 不想获取一次数据,可以把修改后的实体Attach到DbContext.Set<T>中,显示修改Entry的状态为Modified,修改后调用SaveChanges,此时EF生成的sql语句是更新所有字段的
代码如下:
using (MatureCMSDbContext dbContext = new MatureCMSDbContext()) { Category category = new Category(); category.Id = 1; category.Name = "Test name"; dbContext.Set<Category>().Attach(category); dbContext.Entry<Category>(category).State = System.Data.EntityState.Modified; dbContext.SaveChanges(); }
生成的SQL为:
exec sp_executesql N'update [dbo].[Categories] set [Name] = @0, [Description] = null, [ParentCategoryId] = null where ([Id] = @1) ',N'@0 nvarchar(20),@1 bigint',@0=N'Test name',@1=1
如果想要访问数据库一次且又能根据属性的变化动态生成部分字段的更新呢?这就是我写这篇文章的目地
下面是我写的一个代理类的生成器DynamicProxyGenerator,具体代码自己看吧,应该都能看懂
public class DynamicProxyGenerator { private const string DynamicAssemblyName = "DynamicAssembly";//动态程序集名称 private const string DynamicModuleName = "DynamicAssemblyModule"; private const string DynamicModuleDllName = "DynamicAssembly.dll";//动态模块名称 private const string ProxyClassNameFormater = "{0}Proxy"; private const string ModifiedPropertyNamesFieldName = "ModifiedPropertyNames"; private const MethodAttributes GetSetMethodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.CheckAccessOnOverride | MethodAttributes.Virtual | MethodAttributes.HideBySig; /// <summary> /// 创建动态程序集,返回AssemblyBuilder /// </summary> /// <param name="isSavaDll"></param> /// <returns></returns> private static AssemblyBuilder DefineDynamicAssembly(bool isSavaDll = false) { //动态创建程序集 AssemblyName DemoName = new AssemblyName(DynamicAssemblyName); AssemblyBuilderAccess assemblyBuilderAccess = isSavaDll ? AssemblyBuilderAccess.RunAndSave : AssemblyBuilderAccess.Run; AssemblyBuilder dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(DemoName, assemblyBuilderAccess); return dynamicAssembly; } /// <summary> /// 创建动态模块,返回ModuleBuilder /// </summary> /// <param name="isSavaDll"></param> /// <returns>ModuleBuilder</returns> private static ModuleBuilder DefineDynamicModule(AssemblyBuilder dynamicAssembly, bool isSavaDll = false) { ModuleBuilder moduleBuilder = null; //动态创建模块 if(isSavaDll) moduleBuilder = dynamicAssembly.DefineDynamicModule(DynamicModuleName, DynamicModuleDllName); else moduleBuilder = dynamicAssembly.DefineDynamicModule(DynamicModuleName); return moduleBuilder; } /// <summary> /// 创建动态代理类,重写属性Get Set 方法,并监控属性的Set方法,把变更的属性名加入到list集合中,需要监控的属性必须是virtual /// 如果你想保存修改的属性名和属性值,修改Set方法的IL实现 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="isSavaDynamicModule"></param> /// <returns></returns> public static T CreateDynamicProxy<T>(bool isSavaDynamicModule = false) { Type modifiedPropertyNamesType = typeof(HashSet<string>); Type typeNeedProxy = typeof(T); AssemblyBuilder assemblyBuilder = DefineDynamicAssembly(isSavaDynamicModule); //动态创建模块 ModuleBuilder moduleBuilder = DefineDynamicModule(assemblyBuilder,isSavaDynamicModule); string proxyClassName = string.Format(ProxyClassNameFormater, typeNeedProxy.Name); //动态创建类代理 TypeBuilder typeBuilderProxy = moduleBuilder.DefineType(proxyClassName, TypeAttributes.Public, typeNeedProxy); //定义一个变量存放属性变更名 FieldBuilder fbModifiedPropertyNames = typeBuilderProxy.DefineField(ModifiedPropertyNamesFieldName, modifiedPropertyNamesType, FieldAttributes.Public); /* * 构造函数 实例化 ModifiedPropertyNames,生成类似于下面的代码 ModifiedPropertyNames = new List<string>(); */ ConstructorBuilder constructorBuilder = typeBuilderProxy.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null); ILGenerator ilgCtor = constructorBuilder.GetILGenerator(); ilgCtor.Emit(OpCodes.Ldarg_0);//加载当前类 ilgCtor.Emit(OpCodes.Newobj, modifiedPropertyNamesType.GetConstructor(new Type[0]));//实例化对象入栈 ilgCtor.Emit(OpCodes.Stfld, fbModifiedPropertyNames);//设置fbModifiedPropertyNames值,为刚入栈的实例化对象 ilgCtor.Emit(OpCodes.Ret);//返回 //获取被代理对象的所有属性,循环属性进行重写 PropertyInfo[] properties = typeNeedProxy.GetProperties(); foreach (PropertyInfo propertyInfo in properties) { string propertyName = propertyInfo.Name; Type typePepropertyInfo = propertyInfo.PropertyType; //动态创建字段和属性 FieldBuilder fieldBuilder = typeBuilderProxy.DefineField("_" + propertyName, typePepropertyInfo, FieldAttributes.Private); PropertyBuilder propertyBuilder = typeBuilderProxy.DefineProperty(propertyName, PropertyAttributes.SpecialName, typePepropertyInfo, null); //重写属性的Get Set方法 var methodGet = typeBuilderProxy.DefineMethod("get_" + propertyName, GetSetMethodAttributes, typePepropertyInfo, Type.EmptyTypes); var methodSet = typeBuilderProxy.DefineMethod("set_" + propertyName, GetSetMethodAttributes, null, new Type[] { typePepropertyInfo }); //il of get method var ilGetMethod = methodGet.GetILGenerator(); ilGetMethod.Emit(OpCodes.Ldarg_0); ilGetMethod.Emit(OpCodes.Ldfld, fieldBuilder); ilGetMethod.Emit(OpCodes.Ret); //il of set method ILGenerator ilSetMethod = methodSet.GetILGenerator(); ilSetMethod.Emit(OpCodes.Ldarg_0); ilSetMethod.Emit(OpCodes.Ldarg_1); ilSetMethod.Emit(OpCodes.Stfld, fieldBuilder); ilSetMethod.Emit(OpCodes.Ldarg_0); ilSetMethod.Emit(OpCodes.Ldfld, fbModifiedPropertyNames); ilSetMethod.Emit(OpCodes.Ldstr, propertyInfo.Name); ilSetMethod.Emit(OpCodes.Callvirt, modifiedPropertyNamesType.GetMethod("Add", new Type[] { typeof(string) })); ilSetMethod.Emit(OpCodes.Pop); ilSetMethod.Emit(OpCodes.Ret); //设置属性的Get Set方法 propertyBuilder.SetGetMethod(methodGet); propertyBuilder.SetSetMethod(methodSet); } //使用动态类创建类型 Type proxyClassType = typeBuilderProxy.CreateType(); //保存动态创建的程序集 if(isSavaDynamicModule) assemblyBuilder.Save(DynamicModuleDllName); //创建类实例 var instance = Activator.CreateInstance(proxyClassType); return (T)instance; } /// <summary> /// 获取属性的变更名称, /// 此处只检测调用了Set方法的属性,不会检测值是否真的有变 /// </summary> /// <param name="obj"></param> /// <returns></returns> public static HashSet<string> GetModifiedProperties(object obj) { FieldInfo fieldInfo = obj.GetType().GetField(ModifiedPropertyNamesFieldName); if (fieldInfo == null) return null; object value = fieldInfo.GetValue(obj); return value as HashSet<string>; }
调用生成器的代码如:
[TestMethod] public void TestCreateDynamicProxy() { User user = DynamicProxyGenerator.CreateDynamicProxy<User>(); user.LoginName = "login name"; user.LoginName = "login name"; user.Password = "login name"; HashSet<string> modifiedPropertyNames = DynamicProxyGenerator.GetModifiedProperties(user) as HashSet<string>; Assert.IsNotNull(modifiedPropertyNames); Assert.AreEqual(2, modifiedPropertyNames.Count); Assert.IsTrue(modifiedPropertyNames.Any(p => p == "LoginName")); Assert.IsTrue(modifiedPropertyNames.Any(p => p == "Password")); }
当然此代理器并没有解决ORM更新部分字段的问题,需要根据不同的ORM去实现,既然已经知道了哪些属性变更了,就很容易了,比如EF中可以把DbContext转化为ObjectContext,然后设置需要更新的属性名,代码如下:
dbContext.Set<TEntity>().Attach(entity); var stateEntry = return (IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity); foreach (string propertyName in modifiedProperty) { stateEntry.SetModifiedProperty(propertyName); }
生成的代理类继承传入的实体并重写实体的属性(属性必须是Virtual的),重写了Set方法,当调用Set方法时就向HashSet<string>中插入属性名(如果想获取属性值,请修改Set方法的IL),并没有比较原始值和最新值是否有值的变更,只要调用了Set方法,就把此属性名加入到属性变更列表中
就写到这里了,有不对的地方欢迎各位指正。
代理类的源代码和测试程序下载地址为:https://files.cnblogs.com/why520crazy/DynamicProxyDemo.rar
前面说的受启发的博主是 http://www.cnblogs.com/daxnet/archive/2012/04/16/2452660.html
Worktile,新一代简单好用、体验极致的团队协同、项目管理工具,让你和你的团队随时随地一起工作。完全免费,现在就去了解一下吧。
https://worktile.com