• 浅尝辄止:通过DynamicMethod和ILGenerator创建实体类


    周末尝试着学习emit进行高级编程。想起若干天前收藏的Herbrandson在codeproject上的神作(此文技术含量其实一般,实际上他讲的就是用DynamicMethodILGenerator实现快速实体创建的方法,但是文笔睿智,用词精致,非常诱人),重温了一下原文,又参考了msdn和园子里几位高手的博客,整理一下自己的学习笔记,希望对您有用。

    一、常见的将DataReader转换为List的方法
    1、类似codesmith的模板生成方式
    熟悉代码生成器的童鞋一定知道的。在实体类里生成了“一堆”字段,属性,数据类型等等相关的判断(不是switch...case就是if...else等等等等)。其实也就类似Herbranson原文里介绍的第一种实现方式。毫无疑问,这种方式简单直接明了,是个程序员都看得懂,但是多数人会嫌弃代码啰嗦,生成的类会显得非常臃肿庞大。
    代码
    public class ManualBuilder
    {
        
    public Person Build(SqlDataReader reader)
        {
            Person person 
    = new Person();

            
    if (!reader.IsDBNull(0))
            {
                person.ID 
    = (Guid)reader[0];
            }

            
    if (!reader.IsDBNull(1))
            {
                person.Name 
    = (string)reader[1];
            }

            
    if (!reader.IsDBNull(2))
            {
                person.Kids 
    = (int)reader[2];
            }

            
    if (!reader.IsDBNull(3))
            {
                person.Active 
    = (bool)reader[3];
            }

            
    if (!reader.IsDBNull(4))
            {
                person.DateOfBirth 
    = (DateTime)reader[4];
            }

            
    return person;
        }
    }

    2、反射,反射,兴高采烈地登场
    大家都觉得上面第一种的实现代码毫无美感,通过反射,世界就清静多了:

    代码
    public class ReflectionBuilder</>
    {
        
    private PropertyInfo[] properties;

        
    private ReflectionBuilder() { }

        
    public T Build(SqlDataReader reader)
        {
            T result 
    = (T)Activator.CreateInstance(typeof(T));

            
    for (int i = 0; i < reader.FieldCount; i++)
            {
                
    if (properties[i] != null && !reader.IsDBNull(i))
                {
                    properties[i].SetValue(result, reader[i], 
    null);
                }
            }

            
    return result;
        }

        
    public static ReflectionBuilder<t> CreateBuilder(SqlDataReader reader)
        {
            ReflectionBuilder
    <t> result = new ReflectionBuilder<t>();

            result.properties 
    = new PropertyInfo[reader.FieldCount];
            
    for (int i = 0; i < reader.FieldCount; i++)
            {
                result.properties[i] 
    = typeof(T).GetProperty(reader.GetName(i));
            }

            
    return result;
        }
    }

    观察上面的代码,大家会发现程序的通用性大大增强,减少了大量重复的工作,技术含量也是杠杠的。
    ps:前面两种方式的实现代码都是原文里拷贝来的。大家最常见的就是这两种方式,所以懒得写注释了,没什么难度的。
    ps1:通过反射转换实体,其实还有其他写法的,但是主要思想大同小异(厚颜推荐楼猪的关于ado.net的旧文)。

    二、来一点点emit,性能提高不是一点点
    这就是传说已久的通过emit创建动态代理,实现实体的创建:

    代码
    // ========================================================================================
    // Information: provide by Herbrandson
    // Source: http://www.codeproject.com/KB/database/DynamicMethod_ILGenerator.aspx
    // ========================================================================================
    using System;
    using System.Data;
    using System.Reflection;
    using System.Reflection.Emit;

    /// <summary>
    ///Use DynamicMethod and ILGenerator create entity
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DynamicBuilder<T>
    {
        
    private static readonly MethodInfo getValueMethod = typeof(IDataRecord).GetMethod("get_Item"new Type[] { typeof(int) });

        
    private static readonly MethodInfo isDBNullMethod = typeof(IDataRecord).GetMethod("IsDBNull"new Type[] { typeof(int) });

        
    private delegate T Load(IDataRecord dataRecord);

        
    private Load handler;//最终执行动态方法的一个委托 参数是IDataRecord接口

        
    private DynamicBuilder() { }//私有构造函数

        
    public T Build(IDataRecord dataRecord)
        {
            
    return handler(dataRecord);//执行CreateBuilder里创建的DynamicCreate动态方法的委托
        }

        
    public static DynamicBuilder<T> CreateBuilder(IDataRecord dataRecord)
        {
            DynamicBuilder
    <T> dynamicBuilder = new DynamicBuilder<T>();

            
    //定义一个名为DynamicCreate的动态方法,返回值typof(T),参数typeof(IDataRecord)
            DynamicMethod method = new DynamicMethod("DynamicCreate"typeof(T), new Type[] { typeof(IDataRecord) }, typeof(T), true);

            ILGenerator generator 
    = method.GetILGenerator();//创建一个MSIL生成器,为动态方法生成代码

            LocalBuilder result 
    = generator.DeclareLocal(typeof(T));//声明指定类型的局部变量 可以T t;这么理解

            
    //The next piece of code instantiates the requested type of object and stores it in the local variable. 可以t=new T();这么理解
            generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
            generator.Emit(OpCodes.Stloc, result);

            
    for (int i = 0; i < dataRecord.FieldCount; i++)//数据集合,熟悉的for循环 要干什么你懂的 
            {
                PropertyInfo propertyInfo 
    = typeof(T).GetProperty(dataRecord.GetName(i));//根据列名取属性  原则上属性和列是一一对应的关系
                Label endIfLabel = generator.DefineLabel();

                
    if (propertyInfo != null && propertyInfo.GetSetMethod() != null)//实体存在该属性 且该属性有SetMethod方法
                {
                    
    /*The code then loops through the fields in the data reader, finding matching properties on the type passed in. 
                     * When a match is found, the code checks to see if the value from the data reader is null.
                     
    */
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldc_I4, i);
                    generator.Emit(OpCodes.Callvirt, isDBNullMethod);
    //就知道这里要调用IsDBNull方法 如果IsDBNull==true contine
                    generator.Emit(OpCodes.Brtrue, endIfLabel);

                    
    /*If the value in the data reader is not null, the code sets the value on the object.*/
                    generator.Emit(OpCodes.Ldloc, result);
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldc_I4, i);
                    generator.Emit(OpCodes.Callvirt, getValueMethod);
    //调用get_Item方法
                    generator.Emit(OpCodes.Unbox_Any, dataRecord.GetFieldType(i));
                    generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
    //给该属性设置对应值

                    generator.MarkLabel(endIfLabel);
                }
            }

            
    /*The last part of the code returns the value of the local variable*/
            generator.Emit(OpCodes.Ldloc, result);
            generator.Emit(OpCodes.Ret);
    //方法结束,返回

            
    //完成动态方法的创建,并且创建执行该动态方法的委托,赋值到全局变量handler,handler在Build方法里Invoke
            dynamicBuilder.handler = (Load)method.CreateDelegate(typeof(Load));
            
    return dynamicBuilder;
        }
    }

    (1)、因为老外的这个实现方式涉及到emit的知识,大部分开发者可能了解的并不是很深入,楼猪也不是很熟悉,而且楼猪极不习惯emit这种编程风格,感觉它非常破坏代码的美感,这种牺牲色相的高级写法如果在我们的项目中大面积出现,估计大部分童鞋都会抓狂的。好在现在终于有了工具可以“Emit with a human face”了。
    (2)、不管怎样,还是凭借自己学习到的一点知识储备,大胆按照自己的理解,写了点注释,对照原文作者的意思,不见得很烂,就是这么自信,呵呵。
    在外部调用的方式如下(测试代码,无比丑陋,请留意):

    代码
      static void Main(string[] args)
            {
                
    string strConn = @"Data Source=.\sqlexpress;Initial Catalog=TestDb;Persist Security Info=True;User ID=sa;Password=123456";
                
    using (SqlConnection conn = new SqlConnection(strConn))
                {
                    conn.Open();
                    
    string sql = string.Format("SELECT TOP 10 [Id],[FirstName] ,[LastName] ,[Weight] ,[Height] FROM Person(NOLOCK)");
                    
    //string sql = string.Format("SELECT TOP 10 [FirstName] ,[LastName] ,[Height] FROM Person(NOLOCK)");

                    SqlCommand cmd 
    = conn.CreateCommand();
                    cmd.CommandText 
    = sql;
                    SqlDataReader rdr 
    = cmd.ExecuteReader();
                    IList
    <Person> listPersons = new List<Person>();
                    
    while (rdr.Read())
                    {
                        Person model 
    = DynamicBuilder<Person>.CreateBuilder().Build(rdr);
                        listPersons.Add(model);
                    }
                    Console.WriteLine(
    string.Format("Theres are {0} people.", listPersons.Count));
                }
                Console.Read();
            }

    需要说明的是,生成实体的时候,while(rdr.Read())必须判断,原文里没有提及。

    三、改进ado.net快速上手里的DataReader转换成实体对象
    楼猪在旧文里曾经写了个数据转换类(ModelConverter),现在放弃反射的方式,采用Herbrandson介绍的动态代理实现数据实体的创建:

    代码
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Common;
    using System.Reflection;
    using System.Threading;

    namespace AdoNetDataAccess.Core.Obj2Model
    {
        
    using AdoNetDataAccess.Core.Contract;

        
    public sealed class ModelConverter
        {
            
    private static readonly object objSync = new object();

            
    #region query for list

            
    /// <summary>
            
    /// 查询数据表项并转换为对应实体
            
    /// </summary>
            
    /// <typeparam name="T"></typeparam>
            
    /// <param name="objType"></param>
            
    /// <param name="rdr"></param>
            
    /// <returns></returns>
            public static IList<T> QueryForList<T>(string sqlStr, CommandType cmdType, List<DbParameter> listParams, Type objType, IDbOperation dbOperation)
                
    where T : classnew()
            {
                IDataReader rdr 
    = dbOperation.ExecuteReader(sqlStr, cmdType, listParams);
                IList
    <T> listModels = new List<T>();
                
    try
                {
                    Monitor.Enter(objSync);
                    
    while (rdr.Read())
                    {
                        T model 
    = default(T);
                        model 
    = DynamicBuilder<T>.CreateBuilder(rdr).Build(rdr);//通过动态反射转换成实体
                        listModels.Add(model);
                    }

                    
    /*不用有人怨声载道的性能恶劣的反射了*/
                    
    //Hashtable ht = CreateHashColumnName(rdr);
                    
    //while (rdr.Read())
                    
    //{
                    
    //    Object obj = Activator.CreateInstance(objType);
                    
    //    PropertyInfo[] properties = objType.GetProperties();
                    
    //    foreach (PropertyInfo propInfo in properties)
                    
    //    {
                    
    //        string columnName = propInfo.Name.ToUpper();
                    
    //        if (ht.ContainsKey(columnName) == false)
                    
    //        {
                    
    //            continue;
                    
    //        }
                    
    //        int index = rdr.GetOrdinal(propInfo.Name);
                    
    //        object columnValue = rdr.GetValue(index);
                    
    //        if (columnValue != System.DBNull.Value)
                    
    //        {
                    
    //            SetValue(propInfo, obj, columnValue);
                    
    //        }
                    
    //    }
                    
    //    T model = default(T);
                    
    //    model = obj as T;
                    
    //    listModels.Add(model);
                    
    //}
                }
                
    finally
                {
                    rdr.Close();
                    rdr.Dispose();
                    Monitor.Exit(objSync);
                }
                
    return listModels;
            }

            
    #endregion

            
    #region query for dictionary

            
    /// <summary>
            
    /// 查询数据表项并转换为对应实体
            
    /// </summary>
            
    /// <typeparam name="K"></typeparam>
            
    /// <typeparam name="T"></typeparam>
            
    /// <param name="key">字典对应key列名</param>
            
    /// <param name="objType"></param>
            
    /// <param name="rdr"></param>
            
    /// <returns></returns>
            public static IDictionary<K, T> QueryForDictionary<K, T>(string key, string sqlStr, CommandType cmdType, List<DbParameter> listParams, Type objType, IDbOperation dbOperation)
                
    where T : classnew()
            {
                IDataReader rdr 
    = dbOperation.ExecuteReader(sqlStr, cmdType, listParams);
                IDictionary
    <K, T> dictModels = new Dictionary<K, T>();
                
    try
                {
                    Monitor.Enter(objSync);
                    
    while (rdr.Read())
                    {
                        T model 
    = default(T);
                        model 
    = DynamicBuilder<T>.CreateBuilder(rdr).Build(rdr);//通过动态反射转换成实体
                        
    //K objKey = GetModelKey<K, T>(key, model);
                        K objKey = BuildPrimaryKey<K, T>(key, rdr);
                        dictModels.Add(objKey, model);
                    }

                    
    /*不用有人怨声载道的性能恶劣的反射了*/
                    
    //Hashtable ht = CreateHashColumnName(rdr);
                    
    //while (rdr.Read())
                    
    //{
                    
    //    Object obj = Activator.CreateInstance(objType);
                    
    //    PropertyInfo[] properties = objType.GetProperties();
                    
    //    object dictKey = null;
                    
    //    foreach (PropertyInfo propInfo in properties)
                    
    //    {
                    
    //        string columnName = propInfo.Name.ToUpper();
                    
    //        if (ht.ContainsKey(columnName) == false)
                    
    //        {
                    
    //            continue;
                    
    //        }
                    
    //        int index = rdr.GetOrdinal(propInfo.Name);
                    
    //        object columnValue = rdr.GetValue(index);
                    
    //        if (columnValue != System.DBNull.Value)
                    
    //        {
                    
    //            SetValue(propInfo, obj, columnValue);
                    
    //            if (string.Compare(columnName, key.ToUpper()) == 0)
                    
    //            {
                    
    //                dictKey = columnValue;
                    
    //            }
                    
    //        }
                    
    //    }
                    
    //    T model = default(T);
                    
    //    model = obj as T;
                    
    //    K objKey = (K)dictKey;
                    
    //    dictModels.Add(objKey, model);
                    
    //}
                }
                
    finally
                {
                    rdr.Close();
                    rdr.Dispose();
                    Monitor.Exit(objSync);
                }
                
    return dictModels;
            }

            
    #endregion

            
    #region internal util

            
    //private static Hashtable CreateHashColumnName(IDataReader rdr)
            
    //{
            
    //    int len = rdr.FieldCount;
            
    //    Hashtable ht = new Hashtable(len);
            
    //    for (int i = 0; i < len; i++)
            
    //    {
            
    //        string columnName = rdr.GetName(i).ToUpper(); //不区分大小写
            
    //        string columnRealName = rdr.GetName(i);
            
    //        if (ht.ContainsKey(columnName) == false)
            
    //        {
            
    //            ht.Add(columnName, columnRealName);
            
    //        }
            
    //    }
            
    //    return ht;
            
    //}

            
    //private static void SetValue(PropertyInfo propInfo, Object obj, object objValue)
            
    //{
            
    //    try
            
    //    {
            
    //        propInfo.SetValue(obj, objValue, null);
            
    //    }
            
    //    catch
            
    //    {
            
    //        object realValue = null;
            
    //        try
            
    //        {
            
    //            realValue = Convert.ChangeType(objValue, propInfo.PropertyType);
            
    //            propInfo.SetValue(obj, realValue, null);
            
    //        }
            
    //        catch (Exception ex)
            
    //        {
            
    //            string err = ex.Message;
            
    //            //throw ex; //在数据库数据有不符合规范的情况下应该及时抛出异常
            
    //        }
            
    //    }
            
    //}

            
    //private static K GetModelKey<K, T>(string key, T model)
            
    //{
            
    //    object dictKey = null;
            
    //    PropertyInfo[] properties = model.GetType().GetProperties();
            
    //    foreach (PropertyInfo propInfo in properties)
            
    //    {
            
    //        if (string.Compare(propInfo.Name.ToUpper(), key.ToUpper()) == 0)
            
    //        {
            
    //            dictKey = propInfo.GetValue(model, null);
            
    //            break;
            
    //        }
            
    //    }
            
    //    K objKey = (K)dictKey;
            
    //    return objKey;
            
    //}

            
    private static K BuildPrimaryKey<K, T>(string key, IDataRecord dataRecord)
            {
                K dictKey 
    = default(K);
                
    for (int i = 0; i < dataRecord.FieldCount; i++)
                {
                    PropertyInfo propertyInfo 
    = typeof(T).GetProperty(dataRecord.GetName(i));
                    
    if (propertyInfo != null && string.Compare(key.ToUpper(), propertyInfo.Name.ToUpper()) == 0)
                    {
                        
    object value = dataRecord.GetValue(i);
                        dictKey 
    = (K)value;
                        
    break;
                    }
                }
                
    return dictKey;
            }

            
    #endregion
        }
    }


    DynamicBuilder类就是上面介绍的通过DynamicMethod和ILGenerator的方式。这样,QueryForList和QueryForDictionary方法的性能就得到了改善。
    需要说明的是,在实体转换的时候,QueryForDictionary方法的字典的key的获取是通过BuildPrimaryKey泛型方法,直接从IDataReader里取值,而没有通过emit生成动态代理获取(有心的童鞋可以小试牛刀,看看如何通过构建动态代理获取key),个人认为使用emit适可而止,没必要矫枉过正。
      
    先写到这里了。你还在纠结要不要学习emit? Yes,it works.

    demo下载:demo

  • 相关阅读:
    管理者如何保持团队稳定性
    信息系统项目管理师考试之案例分析答题技巧
    【信息系统项目管理师】信息系统项目管理师计算题汇总
    [转]信息系统项目管理师计算题汇总
    Oracle
    PLSQL中查询数据的时候查询结果显示中文乱码
    项目管理知识体系指南(PMBOK 指南)
    linux查看分区命令和根分区扩容方法
    docker启动命令,docker重启命令,docker关闭命令
    iOS与JavaScript交互三: MessageHandler--window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
  • 原文地址:https://www.cnblogs.com/jeffwongishandsome/p/1790057.html
Copyright © 2020-2023  润新知