浅尝辄止:通过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

posted on 2010-08-01 20:25  JeffWong  阅读(5851)  评论(6编辑  收藏  举报