浅尝辄止:通过DynamicMethod和ILGenerator创建实体类
一、常见的将DataReader转换为List的方法
1、类似codesmith的模板生成方式
熟悉代码生成器的童鞋一定知道的。在实体类里生成了“一堆”字段,属性,数据类型等等相关的判断(不是switch...case就是if...else等等等等)。其实也就类似Herbranson原文里介绍的第一种实现方式。毫无疑问,这种方式简单直接明了,是个程序员都看得懂,但是多数人会嫌弃代码啰嗦,生成的类会显得非常臃肿庞大。
{
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、反射,反射,兴高采烈地登场
大家都觉得上面第一种的实现代码毫无美感,通过反射,世界就清静多了:
{
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)、不管怎样,还是凭借自己学习到的一点知识储备,大胆按照自己的理解,写了点注释,对照原文作者的意思,不见得很烂,就是这么自信,呵呵。
在外部调用的方式如下(测试代码,无比丑陋,请留意):
{
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.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 : class, new()
{
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 : class, new()
{
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
作者:Jeff Wong
出处:http://jeffwongishandsome.cnblogs.com/
本文版权归作者和博客园共有,欢迎围观转载。转载时请您务必在文章明显位置给出原文链接,谢谢您的合作。