一、常见的将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