博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
来一点反射和Emit,让ORM的使用极度简化
阅读量:6731 次
发布时间:2019-06-25

本文共 10013 字,大约阅读时间需要 33 分钟。

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" };        }        ///         ///         ///         public System.Int32 UID        {            get { return getProperty
("UID"); } set { setProperty("UID", value); } } ///
/// /// public System.String Name { get { return getProperty
("Name"); } set { setProperty("Name", value, 50); } } ///
/// /// public System.Boolean Sex { get { return getProperty
("Sex"); } set { setProperty("Sex", value); } } ///
/// /// public System.Single Height { get { return getProperty
("Height"); } set { setProperty("Height", value); } } ///
/// /// public System.DateTime Birthday { get { return getProperty
("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
propertyNames = new List
(); 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方式得到属性访问器的具体构造过程:

///         /// 构造Get访问器        ///         ///         ///         ///         ///         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);        }        ///         /// 构造Set访问器        ///         ///         ///         ///         ///         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
dictEntityType = new Dictionary
(); private static object sync_lock = new object(); ///
/// 根据接口类型,创建实体类的实例 /// ///
///
public static T CreateEntity
() 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; }    }

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

 

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

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

在代码中,只需要

 EntityBuilder.CreateEntity<ITable_User>();

这样的方式,定义一个实体类的接口,就自动创建了我们的实体类,是不是非常简单了?

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

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

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

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

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

 

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

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

欢迎加入,做最快最好的开发框架!

 

 

 

 

 

 

 

 

转载地址:http://ujfqo.baihongyu.com/

你可能感兴趣的文章
win7配置host添加本地域名测试以及代理和host共存的解决方案
查看>>
Exchange Server 2010高可用性和高性能解决方案
查看>>
安装配置 flannel - 每天5分钟玩转 Docker 容器技术(59)
查看>>
AngularJs ngHref、ngSrc、ngCopy/ngCut/ngPaste
查看>>
erlang的启动参数设置。
查看>>
写给MongoDB开发者的50条建议Tip3
查看>>
关于“幽灵架构”的总结:适用场景与方法重载
查看>>
My Favorite Open Source Security Tools
查看>>
在pc下安装solaris 10
查看>>
1253 Dungeon Master
查看>>
现任明教教主CCNP交换安全第一部分共享
查看>>
动态生成的 Transact-SQL 语句
查看>>
Java正则系列: (1)入门教程
查看>>
云计算服务推动口令破译技术走向新时代
查看>>
深入浅出话多态(上)——具体而微
查看>>
圆形图片的制作
查看>>
RASP解决方案包括开源方案
查看>>
CoordinatorLayout之初步认识
查看>>
mongoose学习笔记1--基础知识2
查看>>
App-V 精品博文翻译系列(4):用App-V加快Windows 7的部署进程
查看>>