zoukankan      html  css  js  c++  java
  • 使用Emit创建DBContext对象

        在EntityFramework Code First的示例中,一般情况下都是要创建一个继承DBContext的类,然后在此类中声明若干DBSet<>的属性,然后才可以使用。最近我就遇到一件为难的事情,项目中的业务对象较多,有一大半是继承了一个自定义的基类ModelBase,如果按照以往的方式就不得不在DBContext里面声明长长的属性,其实就是想有个简便的办法,加上如果后续增加了ModelBase的子类,也不想再去修改DBContext的代码,于是一个念头产生了。
        最初我尝试用反射的方式,定义一个方法,传入我想创建的业务对象的类型(Type),结果发现这样是没有办法对一个已存在的类(DBContext)去增加泛型属性DBSet<Type>的。
        之后又尝试了创建一个EntityTypeConfiguration<ModelBase>的配置类,然后在其中利用反射,为当前程序集中所有子类都配置映射关系,结果发现基类无可避免地被映射成了一张表,因为EntityTypeConfiguration<ModelBase>的原因,首先就纳入到了DBContext之中了。这并不是我所期望的效果。
        于是,我祭出了杀手锏---Emit。基本思路就是动态创建继承DBContext的一个子类对象,根据传入的类来决定是创建其本身或是其子类的DBSet<>属性,这样可控性比较强。
        先用一个简单的例子来说明一下。声明一个类

        public class Author
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }

        如果按照旧方法,原本应该这样声明

        public class Blog : DbContext
        {
            public DbSet<Author> Authors { get; set; }
        }

        现在换成如下方式。

            public static DbContext CreateInstance()
            {
                AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("TestContext"), AssemblyBuilderAccess.RunAndSave);
                ModuleBuilder mb = ab.DefineDynamicModule("TestContext");
                //创建指定名称的类型,注意此名称必须要符合EF的约定,即与连接字符串配置中的Name一致
                TypeBuilder tb = mb.DefineType("Blog", TypeAttributes.Public, typeof(DbContext));
                PropertyBuilder pbAuthor = tb.DefineProperty("Authors", PropertyAttributes.HasDefault, typeof(DbSet<>).MakeGenericType(typeof(Author)), null);
                //创建私有字段,用于属性的读写
                //不能像c#源代码那样直接声明get/set,因为本质上编译后还是有私有字段的
                FieldBuilder fbAuthors = tb.DefineField("_authors", typeof(DbSet<>).MakeGenericType(typeof(Author)), FieldAttributes.Private);
                //Get方法部分
                System.Reflection.MethodAttributes methodAttributes = System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.HideBySig;
                MethodBuilder mbGet = tb.DefineMethod("get_Authors", methodAttributes);
                ILGenerator iL = mbGet.GetILGenerator();
                Label label = iL.DefineLabel();
                iL.Emit(OpCodes.Ldarg_0);
                iL.Emit(OpCodes.Ldfld, fbAuthors);
                iL.Emit(OpCodes.Stloc_0);
                iL.Emit(OpCodes.Br_S, label);
                iL.MarkLabel(label);
                iL.Emit(OpCodes.Ldloc_0);
                iL.Emit(OpCodes.Ret);
                //Set方法部分
                MethodBuilder mbSet = tb.DefineMethod("set_Authors", methodAttributes);
                mbSet.SetParameters(typeof(System.Data.Entity.DbSet<>).MakeGenericType(typeof(Author)));
                ParameterBuilder value = mbSet.DefineParameter(1, ParameterAttributes.None, "value");
                iL = mbSet.GetILGenerator();
                iL.Emit(OpCodes.Ldarg_0);
                iL.Emit(OpCodes.Ldarg_1);
                iL.Emit(OpCodes.Stfld, fbAuthors);
                iL.Emit(OpCodes.Ret);
                //将定义的方法引用与属性相关联
                pbAuthor.SetGetMethod(mbGet);
                pbAuthor.SetSetMethod(mbSet);
                DbContext blog = (DbContext)Activator.CreateInstance(tb.CreateType());  
                return blog;
            }

          最后来一段测试的代码

    using (DbContext blog = CreateInstance())
    {
    Author author
    = new Author { Name = "123" }; blog.Set<Author>().Add(author); blog.SaveChanges(); }

          总结一下,其实Emit创建DbContext的重心就在于对每一个需要操作的业务对象都创建一对Get/Set方法,上述代码可以进一步提炼,单独写成一个方法,传入不同的Type替换掉Author所占的位置即可。此方法很另类,性能上也并非很理想(在对象数量比较庞大的情况下反而效率较高),因此在使用EF的过程中就初始化的时候创建一次即可,在有对象的变更或增减时,可以随着Migration(EF4.3以后新增特性)一起更新。

  • 相关阅读:
    前端性能优化:Add Expires headers
    HTTP请求header信息讲解
    虚拟机的三种网络模式
    loadrunner中pacing设置01
    loadrunner中pacing的设置
    mysql安全策略
    Linux安装配置apache
    同步加载、异步加载、延迟加载
    monitorix(linux)系统和网络监控公工具
    HTTP与HTTPS对访问速度(性能)的影响
  • 原文地址:https://www.cnblogs.com/BeanHsiang/p/2482808.html
Copyright © 2011-2022 走看看