zoukankan      html  css  js  c++  java
  • 来一点反射和Emit,让ORM的使用极度简化

    PDF.NET开发框架一直是号称“无需反射”的,因为它的ORM框架(PDF.NET不仅仅是一个ORM框架,详细请见官网)中实体类的设计很特别,不需要反射就能够获知映射的字段信息,我们用实际的例子来说明下。

    1,实体类解析

    假设有这样一个数据库LocalDb中有一个表Table_User ,如下图:

    图中的数据库用PDF.NET集成开发工具打开,该工具可以在官网找到下载地址。找到该表后,在左边的表名称树节点或者右边的查询窗口,鼠标右键菜单上,找到生成实体类的功能,具体过程这里不做演示了,因为这不是本文的主题。

    下面,我们看看生成的实体类:

     [Serializable()]
        public partial class Table_User : EntityBase    {
            public Table_User()
            {
                TableName = "Table_User";
                EntityMap = EntityMapType.Table;
                //IdentityName = "标识字段名";
                IdentityName = "UID";
    
                //PrimaryKeys.Add("主键字段名");
                PrimaryKeys.Add("UID");
    
    
            }
    
    
            protected override void SetFieldNames()
            {
                PropertyNames = new string[] { "UID", "Name", "Sex", "Height", "Birthday" };
            }
    
    
    
            /// <summary>
            /// 
            /// </summary>
            public System.Int32 UID
            {
                get { return getProperty<System.Int32>("UID"); }
                set { setProperty("UID", value); }
            }
    
            /// <summary>
            /// 
            /// </summary>
            public System.String Name
            {
                get { return getProperty<System.String>("Name"); }
                set { setProperty("Name", value, 50); }
            }
    
            /// <summary>
            /// 
            /// </summary>
            public System.Boolean Sex
            {
                get { return getProperty<System.Boolean>("Sex"); }
                set { setProperty("Sex", value); }
            }
    
            /// <summary>
            /// 
            /// </summary>
            public System.Single Height
            {
                get { return getProperty<System.Single>("Height"); }
                set { setProperty("Height", value); }
            }
    
            /// <summary>
            /// 
            /// </summary>
            public System.DateTime Birthday
            {
                get { return getProperty<System.DateTime>("Birthday"); }
                set { setProperty("Birthday", value); }
            }
    
    
        }

    在实体类的构造函数中,下面几个属性指明了表的一些特性:

    TableName = "Table_User";
    表示实体类映射的表名称;

    EntityMap = EntityMapType.Table;
    表示实体类的映射类型是一个表,当然还可以是视图、存储过程、函数等;

    //IdentityName = "标识字段名";
    IdentityName = "UID";
    
     //PrimaryKeys.Add("主键字段名"); 
    PrimaryKeys.Add("UID");

    这个不用多说,有注释了。注意主键可以设置多个的。

    protected override void SetFieldNames()
    该方法说明了实体类映射的哪些字段。

    public System.Int32 UID
            {
               
    get { return getProperty<System.Int32>("UID"); }
               
    set { setProperty("UID", value); }
            }

    UID属性的Get和Set方法也很简单,看名字就知道它的功能了。注意属性中映射了字段名称,比如数据库的字段是UID,那么属性改个名字,象下面这样写也是完全可以的:

    public System.Int32 UserId
            {
               
    get { return getProperty<System.Int32>("UID"); }
               
    set { setProperty("UID", value); }
            }

    2,问题和优化

    因此,从总体上来说,PDF.NET实体类的结构很简单,比起EF的DbFirst方式和其它ORM框架的实体类来说,要简单很多,所以我一般情况下都是手写实体类,但是对于不是很熟悉框架的朋友来说,如果没有代码工具,要手写还是比较麻烦,毕竟属性的Get和Set访问器还是要多写一行代码。

    如果我们将实体类先抽象出来一个接口,然后让框架根据该接口,自动继承EntityBase基类和实现接口的属性方法,那该多好啊!

    PS:这个想法我已经想了好几年了,但总觉得不是很有必要。现在,CodeFirst越来越流行了,都是先定义实体类,然后在定义或者自动创建数据库。同样,PDF.NET的广大用户也要求能够更简单的使用框架,跟上时代潮流。所以,我最近才付诸实际行动。

    我们用一点反射和一点Emit,来完成这个过程:

    反射得到构造函数和属性定义:

               //得到类型生成器            
                TypeBuilder typeBuilder = modBuilder.DefineType(newTypeName, newTypeAttribute, newTypeParent, newTypeInterfaces);
                typeBuilder.AddInterfaceImplementation(targetType);
    
                //定义构造函数
                BuildConstructor(typeBuilder, newTypeParent, targetType.Name);
    
                //以下将为新类型声明方法:新类型应该override基类型的所以virtual方法
                PropertyInfo[] pis = targetType.GetProperties();
                List<string> propertyNames = new List<string>();
    
                foreach (PropertyInfo pi in pis)
                {
                    propertyNames.Add(pi.Name);
                    //属性构造器
                    PropertyBuilder propBuilder = typeBuilder.DefineProperty(pi.Name,
                        System.Reflection.PropertyAttributes.HasDefault,
                        pi.PropertyType,
                        null);
    
                    MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Final;
                    //构造Get访问器
                    MethodBuilder getPropMethodBuilder = typeBuilder.DefineMethod("get_" + pi.Name,
                        getSetAttr, 
                        pi.PropertyType, 
                        Type.EmptyTypes);
                    GeterIL(pi.Name, newTypeParent, pi.PropertyType, getPropMethodBuilder);
                    //构造Set访问器
                    MethodBuilder setPropMethodBuilder = typeBuilder.DefineMethod("set_" + pi.Name, 
                        getSetAttr,
                        null,
                        new Type[] { pi.PropertyType });
                    SeterIL(pi.Name, newTypeParent, pi.PropertyType, setPropMethodBuilder);
                    //添加到属性构造器
                    propBuilder.SetGetMethod(getPropMethodBuilder);
                    propBuilder.SetSetMethod(setPropMethodBuilder);
                }
    
                MethodBuilder SetFieldNamesBuilder = typeBuilder.DefineMethod("SetFieldNames", MethodAttributes.Family  | MethodAttributes.Virtual | MethodAttributes.HideBySig);
                SetFieldNamesIL(newTypeParent, SetFieldNamesBuilder, propertyNames.ToArray());
                
                //真正创建,并返回
                Type resuleType=typeBuilder.CreateType();

    Emit方式得到属性访问器的具体构造过程:

            /// <summary>
            /// 构造Get访问器
            /// </summary>
            /// <param name="propertyName"></param>
            /// <param name="baseType"></param>
            /// <param name="propertyType"></param>
            /// <param name="methodBuilder"></param>
            void GeterIL(string propertyName, Type baseType, Type propertyType, MethodBuilder methodBuilder)
            {
                MethodInfo getProperty = null;
    
                MethodInfo[] ms = typeof(EntityBase).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
                foreach (MethodInfo info in ms)
                {
                    if (info.Name == "getProperty" && info.IsGenericMethod)
                    {
                        getProperty = info;
                        break;
                    }
                }
                getProperty = getProperty.MakeGenericMethod(propertyType);
    
                var ilGenerator = methodBuilder.GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldstr, propertyName);
                ilGenerator.Emit(OpCodes.Call, getProperty);
                ilGenerator.Emit(OpCodes.Ret);
            }
            /// <summary>
            /// 构造Set访问器
            /// </summary>
            /// <param name="propertyName"></param>
            /// <param name="baseType"></param>
            /// <param name="propertyType"></param>
            /// <param name="methodBuilder"></param>
            void SeterIL(string propertyName, Type baseType, Type propertyType, MethodBuilder methodBuilder)
            {
                MethodInfo setProperty =null;//= baseType.GetMethod("setProperty", BindingFlags.Instance | BindingFlags.NonPublic);
                MethodInfo[] ms = typeof(EntityBase).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
                foreach (MethodInfo info in ms)
                {
                    if (info.Name == "setProperty" )
                    {
                        if (info.GetParameters().Length == 2)
                        {
                            setProperty = info;
                            break;
                        }
                    }
                }
    
                var ilGenerator = methodBuilder.GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldstr, propertyName);
                ilGenerator.Emit(OpCodes.Ldarg_1);
                //是否是值类型
                if (propertyType.IsValueType)
                    ilGenerator.Emit(OpCodes.Box, propertyType);
                ilGenerator.Emit(OpCodes.Call, setProperty);
                ilGenerator.Emit(OpCodes.Ret);
            }

    在上面的IL代码方法中,EntityBase 的 getPropertysetProperty  方法有泛型实现和重载,所以只有遍历实体类所有的方法。

    写Emit代码也不是想象中的那么复杂,基本过程就是先手工写好C#代码,编译得到Exe或者Dll,然后用ILDASM或反编译工具,得到IL代码,最后就是看着IL代码,用Emit一个个对应发出代码,就行了。

    OK,我们将这个代码封装到一个EntityBuilder类中,定一个构造实体类的方法

             private static Dictionary<Type, Type> dictEntityType = new Dictionary<Type, Type>();
            private static object sync_lock = new object();
    
            /// <summary>
            /// 根据接口类型,创建实体类的实例
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <returns></returns>
            public static T CreateEntity<T>() where T:class
            {
                Type targetType = null;
                Type sourceType = typeof(T);
                if (sourceType.BaseType == typeof(EntityBase)) //如果本身是实体类,则不生成
                {
                    targetType = sourceType;
                }
                else
                {
                    if (!dictEntityType.TryGetValue(sourceType, out targetType))
                    {
                        lock (sync_lock)
                        {
                            if (!dictEntityType.TryGetValue(sourceType, out targetType))
                            {
                                EntityBuilder builder = new EntityBuilder(sourceType);
                                targetType = builder.Build();
                                dictEntityType[sourceType] = targetType;
                            }
                        }
                    }
                }
                
                
                
                T entity = (T)Activator.CreateInstance(targetType);
                return entity;
            }

    万事俱备,只欠东风!

    3,更简单的使用方式

    下面,我们将前面的实体类抽象出一个接口ITable_User :

        public interface ITable_User
        {
            DateTime Birthday { get; set; }
            float Height { get; set; }
            string Name { get; set; }
            bool Sex { get; set; }
            int UID { get; set; }
        }

    再做一点准备工作,在应用程序配置文件里面配置一下连接,框架默认取最后一个配置:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <connectionStrings>
            <add name="local" 
    connectionString="Data Source=.;Initial Catalog=LocalDB;Integrated Security=True" 
    providerName="SqlServer" />
            
        </connectionStrings>
    </configuration>

    然后像下面这样使用实体类并查询:

    static void TestDynamicEntity()
            {
                ITable_User user = EntityBuilder.CreateEntity<ITable_User>();
                //如果接口的名称不是"ITableName" 这样的格式,那么需要调用 MapNewTableName方法指定
                //((EntityBase)user).MapNewTableName("Table_User");
    
                OQL qUser = OQL.From((EntityBase)user).Select(user.UID, user.Name, user.Sex).END;
                List<ITable_User> users = EntityQuery.QueryList<ITable_User>(qUser, MyDB.Instance);
            }

    在代码中,只需要

     EntityBuilder.CreateEntity<ITable_User>();
    这样的方式,定义一个实体类的接口,就自动创建了我们的实体类,是不是非常简单了?

    有了实体类,然后可以像普通实体类那样来使用ORM查询语言--OQL,不过原来的EntityQuery泛型实体查询类得改进下,才可以支持“动态实体类”的查询。

    当前功能已经在PDF.NET Ver 4.6.4.0525 版本实现,之前的版本,大家可以去开源项目下载:http://pwmis.codeplex.com

    4,动态实体类的使用约束

    这里说的“动态实体类”是通过程序在运行时动态创建得到实体类,而不是预先在源码中写好的实体类。对本方案而言,使用动态实体类有以下几点约束:

    1. 使用接口(interface)定义实体类
    2. 实体类属性定义需要get,set 访问器同时存在(否则怎么保存数据到数据库?)
    3. 属性名称跟表字段名称一致,且属性类型跟字段的数据类型相兼容
    4. 接口名称为“I”打头的表名称,否则需要使用时候映射一下

    如果你不想有这些约束,或者想灵活映射字段和属性,那么还是手写实体类吧,多写一行代码,象本文开头示例的那个实体类一样。

    -----------------------------------------

    欢迎加入PDF.NET开源技术团队,做最快最好的开发框架!

  • 相关阅读:
    教程:在 Visual Studio 中开始使用 Flask Web 框架
    教程:Visual Studio 中的 Django Web 框架入门
    vs2017下发现解决python运行出现‘No module named "XXX""的解决办法
    《sqlite权威指南》读书笔记 (一)
    SQL Server手工插入标识列
    hdu 3729 I'm Telling the Truth 二分图匹配
    HDU 3065 AC自动机 裸题
    hdu 3720 Arranging Your Team 枚举
    virtualbox 虚拟3台虚拟机搭建hadoop集群
    sqlserver 数据行统计,秒查语句
  • 原文地址:https://www.cnblogs.com/bluedoctor/p/3100049.html
Copyright © 2011-2022 走看看