惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Y
Y Combinator Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
博客园 - 司徒正美
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog

博客园 - 路过秋天

广电系列大流量卡选购指南:【2025-2026年度】 苹果开发者账号付款注意事项:账号实名只能用一次,DNUS注册流程又变了 苹果开发者账号付款注意事项 Taurus.MVC 微服务框架 入门开发教程:项目集成:7、微服务间的调用方式(调整):引用程序集及代码下载(新增) 一招教你戒掉抖音的瘾 .NET Emit 入门教程:第六部分:IL 指令:9:详解 ILGenerator 指令方法:运算操作指令(指令篇结束) Taurus.MVC 性能压力测试(ap 压测 和 linux 下wrk 压测):.NET 版本 .NET Core 8 部署在 IIS 的简单三步 .NET Emit 入门教程:第六部分:IL 指令:8:详解 ILGenerator 指令方法:类型转换指令 .NET Emit 入门教程:第六部分:IL 指令:7:详解 ILGenerator 指令方法:分支条件指令 Taurus.MVC 性能压力测试(ap 压测 和 linux 下wrk 压测):.NET Core 版本 CentOS 下性能测试工具 wrk 安装 .NET Emit 入门教程:第六部分:IL 指令:6:详解 ILGenerator 指令方法:方法调用指令 .NET Emit 入门教程:第六部分:IL 指令:5:详解 ILGenerator 指令方法:创建实例指令 .NET Emit 入门教程:第六部分:IL 指令:4:详解 ILGenerator 指令方法:参数存储指令 .NET Emit 入门教程:第六部分:IL 指令:3:详解 ILGenerator 指令方法:参数加载指令 .NET Emit 入门教程:第六部分:IL 指令:2:详解 ILGenerator 辅助方法 .NET Emit 入门教程:第六部分:IL 指令:1:概要介绍 .NET Emit 入门教程:第五部分:动态生成方法(MethodBuilder 与 DynamicMethod)
.NET Emit 入门教程:第七部分:实战项目1:将 DbDataReader ...
路过秋天 · 2024-04-29 · via 博客园 - 路过秋天

前言:

经过前面几个部分学习,相信学过的同学已经能够掌握 .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 和 动态代理的领域,但掌握它, 并在合适的场景使用它,则可以获得更高效的解决方案。

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