翼度科技»论坛 编程开发 .net 查看内容

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

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
前言:

经过前面几个部分学习,相信学过的同学已经能够掌握 .NET Emit 这种中间语言,并能使得它来编写一些应用,以提高程序的性能。
随着 IL 指令篇的结束,本系列也已经接近尾声,在这接近结束的最后,会提供几个可供直接使用的示例,以供大伙分析或使用在项目中。
ORM 实现的三个通用阶段:

第一阶段:

在以往新手入门写 ORM 实现的时候,往往会借助代码生成器,来针对整个数据库,生成一个一个的基础增删改查。
用代码生成器提前生成针对性的方法,运行效率高,但开发效率有可维护性低。
第二阶段:

随着对程序进一步的理解,可能会进化的使用反射来替代代码生成器,可以简化掉大量的生成式代码。
但该方向正好相反,运行效率低,开发效率和可维护性高,通过对反射属性加以缓存,可以改善运行效率问题。
第三阶段:

今天给出的项目示例是:
  1. 通过 Emit 实现 ORM 中常用的,通过 ADO.NET 的 DataReader 流读取数据库数据,并将其读取到实体类 这一例子。
复制代码
通过该方法,可以即有高的运行效率,同时又保持开发效率和可维护性。
下面看基础示例:
示例代码:

以下示例代码,取自 CYQ.Data
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Reflection.Emit;
  5. using System.Reflection;
  6. using CYQ.Data.Table;
  7. using CYQ.Data.Tool;
  8. using CYQ.Data.SQL;
  9. using System.Data.Common;
  10. namespace CYQ.Data.Emit
  11. {
  12.     /// <summary>
  13.     /// DbDataReader 转实体
  14.     /// </summary>
  15.     internal static partial class DbDataReaderToEntity
  16.     {
  17.         static Dictionary<Type, Func<DbDataReader, object>> typeFuncs = new Dictionary<Type, Func<DbDataReader, object>>();
  18.         private static readonly object lockObj = new object();
  19.         internal static Func<DbDataReader, object> Delegate(Type t)
  20.         {
  21.             if (typeFuncs.ContainsKey(t))
  22.             {
  23.                 return typeFuncs[t];
  24.             }
  25.             lock (lockObj)
  26.             {
  27.                 if (typeFuncs.ContainsKey(t))
  28.                 {
  29.                     return typeFuncs[t];
  30.                 }
  31.                 DynamicMethod method = CreateDynamicMethod(t);
  32.                 var func = method.CreateDelegate(typeof(Func<DbDataReader, object>)) as Func<DbDataReader, object>;
  33.                 typeFuncs.Add(t, func);
  34.                 return func;
  35.             }
  36.         }
  37.         /// <summary>
  38.         /// 构建一个ORM实体转换器(第1次构建有一定开销时间)
  39.         /// </summary>
  40.         /// <param name="entityType">转换的目标类型</param>
  41.         private static DynamicMethod CreateDynamicMethod(Type entityType)
  42.         {
  43.             #region 创建动态方法
  44.             var readerType = typeof(DbDataReader);
  45.             Type convertToolType = typeof(ConvertTool);
  46.             MethodInfo getValue = readerType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string) }, null);
  47.             MethodInfo changeType = convertToolType.GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(object), typeof(Type) }, null);
  48.             MethodInfo getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle");
  49.             DynamicMethod method = new DynamicMethod("DbDataReaderToEntity", typeof(object), new Type[] { readerType }, entityType);
  50.             var constructor = entityType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { }, null);
  51.             ILGenerator gen = method.GetILGenerator();//开始编写IL方法。
  52.             if (constructor == null)
  53.             {
  54.                 gen.Emit(OpCodes.Ret);
  55.                 return method;
  56.             }
  57.             var instance = gen.DeclareLocal(entityType);//0 : Entity t0;
  58.             gen.DeclareLocal(typeof(object));//1 string s1;
  59.             gen.DeclareLocal(typeof(Type));//2   Type t2;
  60.             gen.Emit(OpCodes.Newobj, constructor);
  61.             gen.Emit(OpCodes.Stloc_0, instance);//t0= new T();
  62.             List<PropertyInfo> properties = ReflectTool.GetPropertyList(entityType);
  63.             if (properties != null && properties.Count > 0)
  64.             {
  65.                 foreach (var property in properties)
  66.                 {
  67.                     SetValueByRow(gen, getValue, changeType, getTypeFromHandle, property, null);
  68.                 }
  69.             }
  70.             List<FieldInfo> fields = ReflectTool.GetFieldList(entityType);
  71.             if (fields != null && fields.Count > 0)
  72.             {
  73.                 foreach (var field in fields)
  74.                 {
  75.                     SetValueByRow(gen, getValue, changeType, getTypeFromHandle, null, field);
  76.                 }
  77.             }
  78.             gen.Emit(OpCodes.Ldloc_0, instance);//t0 加载,准备返回
  79.             gen.Emit(OpCodes.Ret);
  80.             #endregion
  81.             return method;
  82.         }
  83.         private static void SetValueByRow(ILGenerator gen, MethodInfo getValue, MethodInfo changeType, MethodInfo getTypeFromHandle, PropertyInfo pi, FieldInfo fi)
  84.         {
  85.             Type valueType = pi != null ? pi.PropertyType : fi.FieldType;
  86.             string fieldName = pi != null ? pi.Name : fi.Name;
  87.             Label labelContinue = gen.DefineLabel();//定义循环标签;goto;
  88.             gen.Emit(OpCodes.Ldarg_0);//加载 reader 对象
  89.             gen.Emit(OpCodes.Ldstr, fieldName);//设置字段名。
  90.             gen.Emit(OpCodes.Callvirt, getValue);//reader.GetValue(...)
  91.             gen.Emit(OpCodes.Stloc_1);//将索引 1 处的局部变量加载到计算堆栈上。
  92.             gen.Emit(OpCodes.Ldloc_1);//将索引 1 处的局部变量加载到计算堆栈上。
  93.             gen.Emit(OpCodes.Brfalse_S, labelContinue);//if(!a){continue;}
  94.             //-------------新增:o=ConvertTool.ChangeType(o, t);
  95.             if (valueType.Name != "Object")
  96.             {
  97.                 gen.Emit(OpCodes.Ldtoken, valueType);//这个卡我卡的有点久。将元数据标记转换为其运行时表示形式,并将其推送到计算堆栈上。
  98.                 //下面这句Call,解决在 .net 中无法获取Type值,抛的异常:尝试读取或写入受保护的内存。这通常指示其他内存已损坏。
  99.                 gen.Emit(OpCodes.Call, getTypeFromHandle);
  100.                 gen.Emit(OpCodes.Stloc_2);
  101.                 gen.Emit(OpCodes.Ldloc_1);//o
  102.                 gen.Emit(OpCodes.Ldloc_2);
  103.                 gen.Emit(OpCodes.Call, changeType);//Call ChangeType(o,type);=> invoke(o,type) 调用由传递的方法说明符指示的方法。
  104.                 gen.Emit(OpCodes.Stloc_1); // o=GetItemValue(ordinal);
  105.             }
  106.             //-------------------------------------------
  107.             SetValue(gen, pi, fi);
  108.             gen.MarkLabel(labelContinue);//继续下一个循环
  109.         }
  110.         private static void SetValue(ILGenerator gen, PropertyInfo pi, FieldInfo fi)
  111.         {
  112.             if (pi != null && pi.CanWrite)
  113.             {
  114.                 gen.Emit(OpCodes.Ldloc_0);//实体对象obj
  115.                 gen.Emit(OpCodes.Ldloc_1);//属性的值 objvalue
  116.                 EmitCastObj(gen, pi.PropertyType);//类型转换
  117.                 gen.EmitCall(OpCodes.Callvirt, pi.GetSetMethod(), null); // Call the property setter
  118.             }
  119.             if (fi != null)
  120.             {
  121.                 gen.Emit(OpCodes.Ldloc_0);//实体对象obj
  122.                 gen.Emit(OpCodes.Ldloc_1);//属性的值 objvalue
  123.                 EmitCastObj(gen, fi.FieldType);//类型转换
  124.                 gen.Emit(OpCodes.Stfld, fi);//对实体赋值 System.Object.FieldSetter(String typeName, String fieldName, Object val)
  125.             }
  126.         }
  127.         private static void EmitCastObj(ILGenerator il, Type targetType)
  128.         {
  129.             if (targetType.IsValueType)
  130.             {
  131.                 il.Emit(OpCodes.Unbox_Any, targetType);
  132.             }
  133.             else
  134.             {
  135.                 il.Emit(OpCodes.Castclass, targetType);
  136.             }
  137.         }
  138.     }
  139. }
复制代码
示例代码使用示例:
  1. private static List<T> ReaderToListEntity<T>(DbDataReader reader)
  2. {
  3.     List<T> list = new List<T>();
  4.     var func = DbDataReaderToEntity.Delegate(typeof(T));
  5.     while (reader.Read())
  6.     {
  7.         object obj = func(reader);
  8.         if (obj != null)
  9.         {
  10.             list.Add((T)obj);
  11.         }
  12.     }
  13.     return list;
  14. }
复制代码
示例代码使用示例重点讲解:

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

可以适应不同的数据库类型,如果新手使用只是针对某一数据库类型,也可以修改为:SqlDataReader 或 MySqlDataReader 等。
2、Emit 实现中,仅实现读取当前行数据的功能,而读取多行,是在外层封装(即使用示例的封装方法)实现:

这样的好处是可以简化 Emit 的部分实现,同时又保留高效的性能。
3、Emit 实现中,涉及到三个外部方法:

A:List properties = ReflectTool.GetPropertyList(entityType);
该方法是 CYQ.Data 的内部的实现,以缓存反射的属性,可以用以下代码替代:
  1. PropertyInfo[] pInfo = t.GetProperties();
复制代码
B:List fields = ReflectTool.GetFieldList(entityType);
该方法是 CYQ.Data 的内部的实现,以缓存反射的属性,可以用以下代码替代:
  1. FieldInfo[] pInfo = t.GetFields();
复制代码
C:ConvertTool.ChangeType 方法:
方法原型如下,实现全品类类型的安全转换:
  1. public static object ChangeType(object value, Type t)
复制代码
如果生成的实体类和数据库类型保持一致,则可以不需要进行类型转换,加类型转换,是为了可以兼容数据库字段类型和实体类属性类型的不同。
该方法的高效实现,可以参考:ConverTool
总结:

Emit 虽然活跃在 ORM 和 动态代理的领域,但掌握它, 并在合适的场景使用它,则可以获得更高效的解决方案。
当然,前提是你需要对程序 “性能” 有清晰的追求。
 

来源:https://www.cnblogs.com/cyq1162/p/18149511
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具