.NET Emit 入门教程:第七部分:实战项目1:将 DbDataReader 转实体

前言:

经过前面几个部分学习,相信学过的同学已经能够掌握 .NET Emit 这种中间语言,并能使得它来编写一些应用,以提高程序的性能。

随着 IL 指令篇的结束,本系列也已经接近尾声,在这接近结束的最后,会提供几个可供直接使用的示例,以供大伙分析或使用在项目中。

ORM 实现的三个通用阶段:

第一阶段:

在以往新手入门写 ORM 实现的时候,往往会借助代码生成器,来针对整个数据库,生成一个一个的基础增删改查。

用代码生成器提前生成针对性的方法,运行效率高,但开发效率有可维护性低。

第二阶段:

随着对程序进一步的理解,可能会进化的使用反射来替代代码生成器,可以简化掉大量的生成式代码。

但该方向正好相反,运行效率低,开发效率和可维护性高,通过对反射属性加以缓存,可以改善运行效率问题。

第三阶段:

今天给出的项目示例是:

通过 Emit 实现 ORM 中常用的,通过 ADO.NET 的 DataReader 流读取数据库数据,并将其读取到实体类 这一例子。

通过该方法,可以即有高的运行效率,同时又保持开发效率和可维护性。

下面看基础示例:

示例代码:

以下示例代码,取自 CYQ.Data

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
using CYQ.Data.Table;
using CYQ.Data.Tool;
using CYQ.Data.SQL;
using System.Data.Common;

namespace CYQ.Data.Emit
{
 /// 
    /// DbDataReader 转实体
 /// 
    internal static partial class DbDataReaderToEntity
 {

 static Dictionaryobject>> typeFuncs = new Dictionaryobject>>();

 private static readonly object lockObj = new object();

 internal static Funcobject> Delegate(Type t)
 {
 if (typeFuncs.ContainsKey(t))
 {
 return typeFuncs[t];
 }
 lock (lockObj)
 {
 if (typeFuncs.ContainsKey(t))
 {
 return typeFuncs[t];
 }
 DynamicMethod method = CreateDynamicMethod(t);
 var func = method.CreateDelegate(typeof(Funcobject>)) as Funcobject>;
 typeFuncs.Add(t, func);
 return func;
 }
 }

 /// 
 /// 构建一个ORM实体转换器(第1次构建有一定开销时间)
 /// 
 /// 转换的目标类型
 private static DynamicMethod CreateDynamicMethod(Type entityType)
 {


 #region 创建动态方法

 var readerType = typeof(DbDataReader);
 Type convertToolType = typeof(ConvertTool);
 MethodInfo getValue = readerType.GetMethod("get\_Item", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string) }, null);
 MethodInfo changeType = convertToolType.GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(object), typeof(Type) }, null);
 MethodInfo getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle");

 DynamicMethod method = new DynamicMethod("DbDataReaderToEntity", typeof(object), new Type[] { readerType }, entityType);
 var constructor = entityType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { }, null);

 ILGenerator gen = method.GetILGenerator();//开始编写IL方法。
 if (constructor == null)
 {
 gen.Emit(OpCodes.Ret);
 return method;
 }
 var instance = gen.DeclareLocal(entityType);//0 : Entity t0;
 gen.DeclareLocal(typeof(object));//1 string s1;
 gen.DeclareLocal(typeof(Type));//2 Type t2;
 gen.Emit(OpCodes.Newobj, constructor);
 gen.Emit(OpCodes.Stloc\_0, instance);//t0= new T();

 List properties = ReflectTool.GetPropertyList(entityType);
 if (properties != null && properties.Count > 0)
 {
 foreach (var property in properties)
 {
 SetValueByRow(gen, getValue, changeType, getTypeFromHandle, property, null);
 }
 }
 List fields = ReflectTool.GetFieldList(entityType);
 if (fields != null && fields.Count > 0)
 {
 foreach (var field in fields)
 {
 SetValueByRow(gen, getValue, changeType, getTypeFromHandle, null, field);
 }
 }

 gen.Emit(OpCodes.Ldloc\_0, instance);//t0 加载,准备返回
 gen.Emit(OpCodes.Ret);
 #endregion

 return method;
 }
 private static void SetValueByRow(ILGenerator gen, MethodInfo getValue, MethodInfo changeType, MethodInfo getTypeFromHandle, PropertyInfo pi, FieldInfo fi)
 {
 Type valueType = pi != null ? pi.PropertyType : fi.FieldType;
 string fieldName = pi != null ? pi.Name : fi.Name;

 Label labelContinue = gen.DefineLabel();//定义循环标签;goto;

 gen.Emit(OpCodes.Ldarg\_0);//加载 reader 对象
 gen.Emit(OpCodes.Ldstr, fieldName);//设置字段名。
 gen.Emit(OpCodes.Callvirt, getValue);//reader.GetValue(...)
 gen.Emit(OpCodes.Stloc\_1);//将索引 1 处的局部变量加载到计算堆栈上。

 gen.Emit(OpCodes.Ldloc\_1);//将索引 1 处的局部变量加载到计算堆栈上。
 gen.Emit(OpCodes.Brfalse\_S, labelContinue);//if(!a){continue;}

 //-------------新增:o=ConvertTool.ChangeType(o, t);
 if (valueType.Name != "Object")
 {
 gen.Emit(OpCodes.Ldtoken, valueType);//这个卡我卡的有点久。将元数据标记转换为其运行时表示形式,并将其推送到计算堆栈上。
 //下面这句Call,解决在 .net 中无法获取Type值,抛的异常:尝试读取或写入受保护的内存。这通常指示其他内存已损坏。
 gen.Emit(OpCodes.Call, getTypeFromHandle);
 gen.Emit(OpCodes.Stloc\_2);

 gen.Emit(OpCodes.Ldloc\_1);//o
 gen.Emit(OpCodes.Ldloc\_2);
 gen.Emit(OpCodes.Call, changeType);//Call ChangeType(o,type);=> invoke(o,type) 调用由传递的方法说明符指示的方法。
 gen.Emit(OpCodes.Stloc\_1); // o=GetItemValue(ordinal);
 }
 //-------------------------------------------
 SetValue(gen, pi, fi);
 gen.MarkLabel(labelContinue);//继续下一个循环
 }

 private static void SetValue(ILGenerator gen, PropertyInfo pi, FieldInfo fi)
 {
 if (pi != null && pi.CanWrite)
 {
 gen.Emit(OpCodes.Ldloc\_0);//实体对象obj
 gen.Emit(OpCodes.Ldloc\_1);//属性的值 objvalue
 EmitCastObj(gen, pi.PropertyType);//类型转换
 gen.EmitCall(OpCodes.Callvirt, pi.GetSetMethod(), null); // Call the property setter
 }
 if (fi != null)
 {
 gen.Emit(OpCodes.Ldloc\_0);//实体对象obj
 gen.Emit(OpCodes.Ldloc\_1);//属性的值 objvalue
 EmitCastObj(gen, fi.FieldType);//类型转换
 gen.Emit(OpCodes.Stfld, fi);//对实体赋值 System.Object.FieldSetter(String typeName, String fieldName, Object val)
 }
 }
 private static void EmitCastObj(ILGenerator il, Type targetType)
 {
 if (targetType.IsValueType)
 {
 il.Emit(OpCodes.Unbox\_Any, targetType);
 }
 else
 {
 il.Emit(OpCodes.Castclass, targetType);
 }
 }
 }

}

示例代码使用示例:

private static List ReaderToListEntity(DbDataReader reader)
{
 List list = new List();
 var func = DbDataReaderToEntity.Delegate(typeof(T));
 while (reader.Read())
 {
 object obj = func(reader);
 if (obj != null)
 {
 list.Add((T)obj);
 }
 }
 return list;
}

示例代码使用示例重点讲解:

1、Emit 实现中,接收 DbDataReader 做为参数,它是各种 DataReader 的基类:

可以适应不同的数据库类型,如果新手使用只是针对某一数据库类型,也可以修改为:SqlDataReader 或 MySqlDataReader 等。

2、Emit 实现中,仅实现读取当前行数据的功能,而读取多行,是在外层封装(即使用示例的封装方法)实现:

这样的好处是可以简化 Emit 的部分实现,同时又保留高效的性能。

3、Emit 实现中,涉及到三个外部方法:

A:List properties = ReflectTool.GetPropertyList(entityType);

该方法是 CYQ.Data 的内部的实现,以缓存反射的属性,可以用以下代码替代:

PropertyInfo[] pInfo = t.GetProperties();

B:List fields = ReflectTool.GetFieldList(entityType);

该方法是 CYQ.Data 的内部的实现,以缓存反射的属性,可以用以下代码替代:

FieldInfo[] pInfo = t.GetFields();

C:ConvertTool.ChangeType 方法:

方法原型如下,实现全品类类型的安全转换:

public static object ChangeType(object value, Type t)

如果生成的实体类和数据库类型保持一致,则可以不需要进行类型转换,加类型转换,是为了可以兼容数据库字段类型和实体类属性类型的不同。

该方法的高效实现,可以参考:ConverTool:西部世界网友加速器

总结:

Emit 虽然活跃在 ORM 和 动态代理的领域,但掌握它, 并在合适的场景使用它,则可以获得更高效的解决方案。

当然,前提是你需要对程序 "性能" 有清晰的追求。

相关推荐
wrx繁星点点5 分钟前
事务的四大特性(ACID)
java·开发语言·数据库
不写八个12 分钟前
Python办公自动化教程(005):Word添加段落
开发语言·python·word
_.Switch29 分钟前
Python机器学习框架介绍和入门案例:Scikit-learn、TensorFlow与Keras、PyTorch
python·机器学习·架构·tensorflow·keras·scikit-learn
小小娥子34 分钟前
Redis的基础认识与在ubuntu上的安装教程
java·数据库·redis·缓存
DieSnowK36 分钟前
[Redis][集群][下]详细讲解
数据库·redis·分布式·缓存·集群·高可用·新手向
赵荏苒41 分钟前
Python小白之Pandas1
开发语言·python
-XWB-1 小时前
【MySQL】数据目录迁移
数据库·mysql
老华带你飞1 小时前
公寓管理系统|SprinBoot+vue夕阳红公寓管理系统(源码+数据库+文档)
java·前端·javascript·数据库·vue.js·spring boot·课程设计
一眼万里*e1 小时前
fish-speech语音大模型本地部署
python·flask·大模型
我明天再来学Web渗透1 小时前
【hot100-java】【二叉树的层序遍历】
java·开发语言·数据库·sql·算法·排序算法