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

net,emit,dbdatareader · 浏览次数 : 0

小编点评

**代码说明** 该代码提供一个通用方法 `SetValueByRow`,它用于设置实体对象的属性。该方法接收以下参数: * `gen`: ILGenerator 对象,用于执行 IL 代码。 * `getValue`: MethodInfo 对象,用于获取属性的值。 * `changeType`: MethodInfo 对象,用于设置属性的值。 * `getTypeFromHandle`: MethodInfo 对象,用于从数据库中获取属性类型。 * `property`: PropertyInfo 对象,表示要设置属性的属性。 * `entityType`: 实体类型。 **代码逻辑** 1. 获取实体类型中的所有属性信息。 2. 如果属性信息不为空,遍历属性信息,并调用 `SetValueByRow` 方法设置每个属性的值。 3. 调用 `Emit` 方法将属性的值写入目标属性上。 **示例** 假设我们有一个名为 `reader` 的 DbDataReader 对象,并希望将它中的数据绑定到 `List<User>` 中的实体对象中。我们可以使用以下代码实现这一操作: ```csharp var func = DbDataReaderToEntity.Delegate(typeof(User)); List list = ReaderToListEntity(reader); ``` **关键方法** * `SetValueByRow`:该方法将遍历属性信息,并调用 `SetValueByRow` 方法设置每个属性的值。 * `Emit`:该方法用于将属性的值写入目标属性上。 * `EmitCastObj`:该方法用于对目标类型进行类型转换。

正文

前言:

经过前面几个部分学习,相信学过的同学已经能够掌握 .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 转实体相似的内容:

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

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

【c#版本Openfeign】Net8 自带OpenFeign实现远程接口调用

引言 相信巨硬,我们便一直硬。Net版本到现在已经出了7了,8也已经在预览版了,相信在一个半月就会正式发布,其中也有很多拭目以待的新功能了,不仅仅有Apm和Tap的结合,TaskToAscynResult,以及UnsafeAccessor用来获取私有变量,性能比反射,EMIT更高,还有针对Async

C#/.NET这些实用的编程技巧你都会了吗?

DotNet Exercises介绍 DotNetGuide专栏C#/.NET/.NET Core编程常用语法、算法、技巧、中间件、类库练习集,配套详细的文章教程讲解,助你快速掌握C#/.NET/.NET Core各种编程常用语法、算法、技巧、中间件、类库等等。 GitHub开源地址:https:/

.NET开源、简单、实用的数据库文档生成工具

前言 今天大姚给大家分享一款.NET开源(MIT License)、免费、简单、实用的数据库文档(字典)生成工具,该工具支持CHM、Word、Excel、PDF、Html、XML、Markdown等多文档格式的导出:DBCHM。 支持的数据库 SqlServer、MySQL、Oracle、Postg

.NET周刊【7月第2期 2024-07-14】

国内文章 开源GTKSystem.Windows.Forms框架让C# winform支持跨平台运行 https://www.cnblogs.com/easywebfactory/p/18289178 GTKSystem.Windows.Forms框架是一种C# winform应用程序跨平台界面开发

记录荒废了三年的四年.net开发的第一次面试

对象 身在成都小微企业,前两天面试深圳老牌金蝶公司。对我这个荒废了三年光影的人来说,怎一个跨度之大了得?作为人我生第一次面试的,整个面试过程,只能用诡异来形容这次感受。而结尾也是迷迷糊糊中草草收场。 不是很好的开局 我我毕业就进了国企。毕业前,在我想象中,他是一个伟光正的形象。所以我抱着人生值得,未

记一次 .NET某上位视觉程序 离奇崩溃分析

一:背景 1. 讲故事 前段时间有位朋友找到我,说他们有一个崩溃的dump让我帮忙看下怎么回事,确实有太多的人在网上找各种故障分析最后联系到了我,还好我一直都是免费分析,不收取任何费用,造福社区。 话不多说,既然有 dump 来了,那就上 windbg 说话吧。 二:WinDbg 分析 1. 为什么

.NET 9 预览版6发布

微软发布了 .NET 9 的第 6 个预览版,此版本包括对运行时、SDK、.NET MAUI、ASP.NET Core 和 C# 的更新,预览版没有包含太多新的主要功能或特性,因为已接近 .NET 9 开发的最后阶段,该开发计划于 11 月全面发布。Loongarch的Native-AOT代码合进去

.NET周刊【7月第1期 2024-07-07】

国内文章 学习.NET 8 MiniApis入门 https://www.cnblogs.com/hejiale010426/p/18280441 MiniApis是ASP.NET Core中的轻量级框架,用最少的代码和配置创建HTTP API。其特点包括简洁明了、性能卓越、灵活多变、易于学习使用,

.NET 9 预览版 5 发布

微软在6月发布了.NET 9预览版的第五个版本。这个新版本的框架预计将在今年晚些时候正式发布,它是一个标准支持(STS)版本,将在2024年11月12日至2026年5月12日期间在多个操作系统上获得18个月的支持。这个预览版带来了性能改进和一些新特性,例如增强的AI能力、优先级无界通道、Search