前言:

经过前面几个部分学习,相信学过的同学已经能够掌握 .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
{
/// <summary>
/// DbDataReader 转实体
/// </summary>
internal static partial class DbDataReaderToEntity
{ static Dictionary<Type, Func<DbDataReader, object>> typeFuncs = new Dictionary<Type, Func<DbDataReader, object>>(); private static readonly object lockObj = new object(); internal static Func<DbDataReader, object> 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(Func<DbDataReader, object>)) as Func<DbDataReader, object>;
typeFuncs.Add(t, func);
return func;
}
} /// <summary>
/// 构建一个ORM实体转换器(第1次构建有一定开销时间)
/// </summary>
/// <param name="entityType">转换的目标类型</param>
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<PropertyInfo> properties = ReflectTool.GetPropertyList(entityType);
if (properties != null && properties.Count > 0)
{
foreach (var property in properties)
{
SetValueByRow(gen, getValue, changeType, getTypeFromHandle, property, null);
}
}
List<FieldInfo> 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<T> ReaderToListEntity<T>(DbDataReader reader)
{
List<T> list = new List<T>();
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<PropertyInfo> properties = ReflectTool.GetPropertyList(entityType);

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

PropertyInfo[] pInfo = t.GetProperties();

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

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

FieldInfo[] pInfo = t.GetFields();

C:ConvertTool.ChangeType 方法:

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

public static object ChangeType(object value, Type t)

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

该方法的高效实现,可以参考:ConverTool

总结:

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

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

.NET Emit 入门教程:第七部分:实战项目1:将 DbDataReader 转实体的更多相关文章

  1. Docker入门教程(七)Docker API

    Docker入门教程(七)Docker API [编者的话]DockerOne组织翻译了Flux7的Docker入门教程,本文是系列入门教程的第七篇,重点介绍了Docker Registry API和 ...

  2. Photoshop入门教程(七):蒙版

    学习心得:蒙版在Photoshop中也是很常用的,学会使用蒙版,可以提高图像处理能力,并且可以保护原片不被破坏,建议多使用一些蒙版. 蒙板是灰度的,是将不同灰度色值转化为不同的透明度,并作用到它所在的 ...

  3. SpringBoot入门教程(二)CentOS部署SpringBoot项目从0到1

    在之前的博文<详解intellij idea搭建SpringBoot>介绍了idea搭建SpringBoot的详细过程, 并在<CentOS安装Tomcat>中介绍了Tomca ...

  4. Python爬虫入门教程 37-100 云沃客项目外包网数据爬虫 scrapy

    爬前叨叨 2019年开始了,今年计划写一整年的博客呢~,第一篇博客写一下 一个外包网站的爬虫,万一你从这个外包网站弄点外快呢,呵呵哒 数据分析 官方网址为 https://www.clouderwor ...

  5. Java基础教程--安卓入门教程(七)

    关注我,每天都有优质技术文章推送,工作,学习累了的时候放松一下自己. 欢迎大家关注我的微信公众号:「醉翁猫咪」 什么是接口? 接口的基本语法 接口的基本语法(一) 使用interface定义 接口当中 ...

  6. mui初级入门教程(七)— 基于native.js的文件系统管理功能实现

    文章来源:小青年原创发布时间:2016-08-01关键词:mui,nativejs,android转载需标注本文原始地址: http://zhaomenghuan.github.io... 前言 这段 ...

  7. 【图像处理】OpenCV+Python图像处理入门教程(七)图像形态学操作

    图像形态学主要从图像内提取分量信息,该分量信息通常对表达图像的特征具有重要意义.例如,在车牌号码识别中,能够使用形态学计算其重要特征信息,在进行识别时,只需对这些特征信息运算即可.图像形态学在目标视觉 ...

  8. WPF入门教程系列七——布局之WrapPanel与StackPanel(二)

    三. WrapPanel WrapPanel布局面板将各个控件从左至右按照行或列的顺序罗列,当长度或高度不够是就会自动调整进行换行,后续排序按照从上至下或从右至左的顺序进行. Orientation— ...

  9. Unreal Engine 4(虚幻UE4)GameplayAbilities 插件入门教程(七)Ability的信息传递等

    本节及后面的内容将会探索更加有意思的内容,更加逼近实际的使用的内容.我们本节内容不难,讲的是释放Ability时的信息传递: 第一步:创建一个GA称为GA_AOE_BlindVengeance,复仇忌 ...

  10. cocos creator主程入门教程(七)—— MVC架构

    五邑隐侠,本名关健昌,10年游戏生涯,现隐居五邑.本系列文章以TypeScript为介绍语言. 这一篇将介绍在游戏客户端常用的架构MVC架构.一个游戏的MVC如下划分: M:1)单例全局的数据中心Wo ...

随机推荐

  1. [ERROR] “不支持使用 SOAP 编码。SOAP 扩展元素包含 use=“encoded“ “ 无法解析 WSDL。

    下载axis-1_4,地址https://archive.apache.org/dist/ws/axis/1_4/ 解压,进入D:\axis-1_4\lib 执行命令 java -cp mail.ja ...

  2. 鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)

    一.mediaquery 1.概述 媒体查询(mediaquery)它允许根据设备的不同特性(如屏幕大小.屏幕方向.分辨率.颜色深度等)来动态地调整网页的样式和布局. 通过媒体查询,可以为不同的设备定 ...

  3. Windows10基于Visual Studio 2019配置OpenCV4.X

    下载OpenCV OpenCV官网 我们是Windows环境所以选择 Windows 配置环境变量 创建一个Visual Studio项目 配置Visual Studio属性 在包含目录中引入路径: ...

  4. Python 代码混淆工具概述

    在保护Python代码安全方面,有多种混淆工具可供选择,包括 Cython, Nuitka, Pyminifier 和 IPA guard.本文将介绍这些工具的特点和适用情况,以及在实际应用中的注意事 ...

  5. 使用OHOS SDK构建tinyexr

    参照OHOS IDE和SDK的安装方法配置好开发环境. 从github下载源码. 执行如下命令: git clone https://github.com/syoyo/tinyexr.git 进入源码 ...

  6. OpenHarmony开发者论坛正式上线,盖楼赢惊喜好礼~

      你,是否曾遇到OpenHarmony开发难题,却不知找谁解答? 你,是否曾想分享OpenHarmony技术,但没有一个官方投稿平台? 你,是否想加入火热的OpenHarmony开源项目,却不知如何 ...

  7. 掌握 C++ 编译过程:面试中常见问题解析

    C++是一种高级编程语言,但是计算机并不能直接理解它.因此,需要将C++代码翻译成计算机可以理解的机器语言.这个过程就是编译过程,是C++程序从源代码到可执行文件的转换过程,包括预处理.编译.汇编和链 ...

  8. 高并发报错too many clients already或无法创建线程

    高并发报错 too many clients already 或无法创建线程 本文出处:https://www.modb.pro/db/432236 问题现象 高并发执行 SQL,报错"so ...

  9. HarmonyOS开发案例分享:万能卡片也能用来玩游戏

    一.前言 作为一名开发爱好者,从大了讲,我学习并进行HarmonyOS相关开发是为了能为鸿蒙生态建设尽一份绵薄之力,从小了讲,就是为了自己的兴趣.而万能卡片是一个让我非常感兴趣的东西. 很多时候我跟别 ...

  10. 【直播回顾】Hello HarmonyOS进阶课程第五课——原子化服务

    由HDE李洋老师主讲的Hello HarmonyOS进阶系列应用篇第五课<原子化服务>, 已于6月1日晚上 19 点在HarmonyOS社群内成功举行.本节课李洋老师带领大家了解Harmo ...