一、前因和存在的问题

  前面我写了一篇《使用Emit把Datatable转换为对象集合(List<T>)》的博文,其实起源于我自己编写的一个orm工具(见前面几篇博文有介绍),里面已有用emit把datareader转换为List<T>的实现方法,但是需要增加一个把DataTable转换为List<T>的方法,在网上搜索了一些代码,经过改造,加入缓存设计,整理了一下代码结构,简单测试没有问题后就发了《使用Emit把Datatable转换为对象集合(List<T>)》一文,但是不久以后我拿这些代码和我以前写的对datareader的转换的代码比较,发现差异较大,于是仔细对比研究了一下,发现datatable的转换方法存在一个不足。即待转换的datatable的架构被严格限制,不够灵活。

二、分析产生原因

  我们看一下利用emit动态创建一个 把datarow转换为一个实体对象的方法的核心部分代码

  for (int index = ; index < dt.Columns.Count; index++)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(dt.Columns[index].ColumnName,StringComparison.CurrentCultureIgnoreCase);
Label endIfLabel = generator.DefineLabel();
if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
{
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldc_I4, index);
generator.Emit(OpCodes.Callvirt, isDBNullMethod);
generator.Emit(OpCodes.Brtrue, endIfLabel);
generator.Emit(OpCodes.Ldloc, result);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldc_I4, index);
generator.Emit(OpCodes.Callvirt, getValueMethod);
generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
generator.MarkLabel(endIfLabel);
}
}

emit的语法比较奥涩难懂,我们不追究细节,假设datatable的列集合为{"a","b","c"},实体对象E的属性有{a,b,c},粗略模拟生成的代码

大致为

public E Convert(DataRow dr)

{

E e = new E();

....

if(dr[0]!=DBNull.Value)

e.a=dr[0];

if(dr[1]!=DBNull.Value)

e.b=dr[1];

if(dr[2]!=DBNull.Value)

e.c=dr[2];

return e;

}

这里有什么问题呢?就是所生成的代码,是先遍历datatable的列,然后检查实体E中是否含有匹配的属性,如果把此方法缓存起来,下一次调用时,碰巧datatable的架构有所变化,如列数只有两列,执行到  if(dr[2]!=DBNull.Value), 就会出现“索引超出范围”了。所以我认为应该先遍历E的属性,然后检查datatable是否含匹配的列。动态生成的代码应该大致如下

public E Convert(DataRow dr)

{

E e = new E();

if  dr.Table.Columns.Contains("a") && !dr.IsNull("a")

e.a = dr["a"];

if  dr.Table.Columns.Contains("b") && !dr.IsNull("b")

e.b = dr["b"];

if  dr.Table.Columns.Contains("c") && !dr.IsNull("c")

e.c = dr["c"];

return e;

}

上述代码,不管datatable如何变化,都只会转换匹配的列而不会出错。

三、解决的办法和成果

所以,我后来还是把我以前的代码加以改造实现了datatable的转换,并把datareader和datatable两个转换方法合并到一个类下面。

代码如下

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Reflection;
using System.Reflection.Emit;
using System.Web.Caching;
using System.Web; namespace LinFramework
{
/// <summary>
/// 实体转换
/// </summary>
public class EntityConverter
{
//数据类型和对应的强制转换方法的methodinfo,供实体属性赋值时调用
private static Dictionary<Type, MethodInfo> ConvertMethods = new Dictionary<Type, MethodInfo>()
{
{typeof(int),typeof(Convert).GetMethod("ToInt32",new Type[]{typeof(object)})},
{typeof(Int16),typeof(Convert).GetMethod("ToInt16",new Type[]{typeof(object)})},
{typeof(Int64),typeof(Convert).GetMethod("ToInt64",new Type[]{typeof(object)})},
{typeof(DateTime),typeof(Convert).GetMethod("ToDateTime",new Type[]{typeof(object)})},
{typeof(decimal),typeof(Convert).GetMethod("ToDecimal",new Type[]{typeof(object)})},
{typeof(Double),typeof(Convert).GetMethod("ToDouble",new Type[]{typeof(object)})},
{typeof(Boolean),typeof(Convert).GetMethod("ToBoolean",new Type[]{typeof(object)})},
{typeof(string),typeof(Convert).GetMethod("ToString",new Type[]{typeof(object)})}
}; //把datarow转换为实体的方法的委托定义
public delegate T LoadDataRow<T>(DataRow dr);
//把datareader转换为实体的方法的委托定义
public delegate T LoadDataRecord<T>(IDataRecord dr); //emit里面用到的针对datarow的元数据信息
private static readonly AssembleInfo dataRowAssembly = new AssembleInfo(typeof(DataRow));
//emit里面用到的针对datareader的元数据信息
private static readonly AssembleInfo dataRecordAssembly = new AssembleInfo(typeof(IDataRecord)); /// <summary>
/// 构造转换动态方法(核心代码),根据assembly可处理datarow和datareader两种转换
/// </summary>
/// <typeparam name="T">返回的实体类型</typeparam>
/// <param name="assembly">待转换数据的元数据信息</param>
/// <returns>实体对象</returns>
private static DynamicMethod BuildMethod<T>(AssembleInfo assembly)
{
DynamicMethod method = new DynamicMethod(assembly.MethodName + typeof(T).Name, MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(T),
new Type[] { assembly.SourceType }, typeof(EntityContext).Module, true);
ILGenerator generator = method.GetILGenerator();
LocalBuilder result = generator.DeclareLocal(typeof(T));
generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
generator.Emit(OpCodes.Stloc, result); foreach (PropertyInfo property in typeof(T).GetProperties())
{
Label endIfLabel = generator.DefineLabel();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldstr, property.Name);
generator.Emit(OpCodes.Callvirt, assembly.CanSettedMethod);
generator.Emit(OpCodes.Brfalse, endIfLabel);
generator.Emit(OpCodes.Ldloc, result);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldstr, property.Name);
generator.Emit(OpCodes.Callvirt, assembly.GetValueMethod);
if (property.PropertyType.IsValueType || property.PropertyType == typeof(string))
generator.Emit(OpCodes.Call, ConvertMethods[property.PropertyType]);
else
generator.Emit(OpCodes.Castclass, property.PropertyType);
generator.Emit(OpCodes.Callvirt, property.GetSetMethod());
generator.MarkLabel(endIfLabel);
}
generator.Emit(OpCodes.Ldloc, result);
generator.Emit(OpCodes.Ret);
return method;
} /// <summary>
/// 从Cache获取委托 LoadDataRow<T>的方法实例,没有则调用BuildMethod构造一个。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private static LoadDataRow<T> GetDataRowMethod<T>()
{
string key = dataRowAssembly.MethodName + typeof(T).Name;
LoadDataRow<T> load = null;
if (HttpRuntime.Cache[key] == null)
{
load = (LoadDataRow<T>)BuildMethod<T>(dataRowAssembly).CreateDelegate(typeof(LoadDataRow<T>));
HttpRuntime.Cache[key] = load;
}
else
{
load = HttpRuntime.Cache[key] as LoadDataRow<T>;
}
return load;
} /// <summary>
/// 从Cache获取委托 LoadDataRecord<T>的方法实例,没有则调用BuildMethod构造一个。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private static LoadDataRecord<T> GetDataRecordMethod<T>()
{
string key = dataRecordAssembly.MethodName + typeof(T).Name;
LoadDataRecord<T> load = null;
if (HttpRuntime.Cache[key] == null)
{
load = (LoadDataRecord<T>)BuildMethod<T>(dataRecordAssembly).CreateDelegate(typeof(LoadDataRecord<T>));
HttpRuntime.Cache[key] = load;
}
else
{
load = HttpRuntime.Cache[key] as LoadDataRecord<T>;
}
return load;
} public static T ToItem<T>(DataRow dr)
{
LoadDataRow<T> load = GetDataRowMethod<T>();
return load(dr);
} public static List<T> ToList<T>(DataTable dt)
{
List<T> list = new List<T>();
if (dt == null || dt.Rows.Count == )
{
return list;
}
LoadDataRow<T> load = GetDataRowMethod<T>();
foreach (DataRow dr in dt.Rows)
{
list.Add(load(dr));
}
return list;
} public static List<T> ToList<T>(IDataReader dr)
{
List<T> list = new List<T>();
LoadDataRecord<T> load = GetDataRecordMethod<T>();
while (dr.Read())
{
list.Add(load(dr));
}
return list;
} } /// <summary>
/// emit所需要的元数据信息
/// </summary>
public class AssembleInfo
{
public AssembleInfo(Type type)
{
SourceType = type;
MethodName = "Convert" + type.Name + "To";
CanSettedMethod = this.GetType().GetMethod("CanSetted", new Type[] { type, typeof(string) });
GetValueMethod = type.GetMethod("get_Item", new Type[] { typeof(string) });
}
public string MethodName;
public Type SourceType;
public MethodInfo CanSettedMethod;
public MethodInfo GetValueMethod; /// <summary>
/// 判断datareader是否存在某字段并且值不为空
/// </summary>
/// <param name="dr">当前的datareader</param>
/// <param name="name">字段名</param>
/// <returns></returns>
public static bool CanSetted(IDataRecord dr, string name)
{
bool result = false;
for (int i = ; i < dr.FieldCount; i++)
{
if (dr.GetName(i).Equals(name, StringComparison.CurrentCultureIgnoreCase) && !dr[i].Equals(DBNull.Value))
{
result = true;
break;
}
}
return result;
} /// <summary>
/// 判断datarow所在的datatable是否存在某列并且值不为空
/// </summary>
/// <param name="dr">当前datarow</param>
/// <param name="name">字段名</param>
/// <returns></returns>
public static bool CanSetted(DataRow dr, string name)
{
return dr.Table.Columns.Contains(name) && !dr.IsNull(name);
}
}
}

四、如何使用

使用起来就很简单了

List<E> list = EntityConverter.ToList<E>(dr);

...

List<E> list = EntityConverter.ToList<E>(dt);

当然,利用泛型还可以再进一步封装,

      public  List<TEntity> QueryBySQL<TEntity>(string sql)
{ using (IDataReader dr = sqlCommand.ExecuteReader(sql)
{
return EntityConverter.ToList<TEntity>(dr);
} }

实际上实现了把查询结果转换为实体对象,已经具备了orm的核心功能了。因此,如果你有开发自己的orm平台的想法,不妨关注一下,欢迎参与共同研究!如果觉得此文对你有所帮助,请高抬贵鼠,点一下推荐!

再谈使用Emit把Datatable转换为对象集合(List<T>)的更多相关文章

  1. 使用Emit把Datatable转换为对象集合(List<T>)

    Emit生成动态方法部分摘自网上,但是经过修改,加入了对委托的缓存以及类结构的调整,使之调用更简洁方便.大致的思路是:要实现转换datatable到某个指定对象的集合,本质是实现转换一个datarow ...

  2. 对象列表转换为DataTable或DataTable转换为对象列表.

    /**********************************************************************************/ // 说明: 数据转换工具. ...

  3. 【转】给DataTable和DataRow扩展方法,直接转换为对象集合或对象

    /// <summary> /// 类 说 明:给DataTable和DataRow扩展方法,直接转换为对象集合或对象 /// 补充说明:此扩展类可以极大的简化操作,但是性能低下,大数据以 ...

  4. Java技巧——将前端的对象数组通过Json字符串传到后端并转换为对象集合

    Java技巧——将前端的对象数组通过Json字符串传到后端并转换为对象集合 摘要:本文主要记录了如何将将前端的对象数组通过Json字符串传到后端,并在后端将Json字符串转换为对象集合. 前端代码 前 ...

  5. DataTable转换为实体集合

    using System; using System.Collections; using System.Collections.Generic; using System.Data; using S ...

  6. 再谈js对象数据结构底层实现原理-object array map set

    如果有java基础的同学,可以回顾下<再谈Java数据结构—分析底层实现与应用注意事项>:java把内存分两种:一种是栈内存,另一种是堆内存.基本类型(即int,short,long,by ...

  7. [工具类]DataTable与泛型集合List互转

    写在前面 工作中经常遇到datatable与list,对于datatable而言操作起来不太方便.所以有的时候还是非常希望通过泛型集合来进行操作的.所以这里就封装了一个扩展类.也方便使用. 类 方法中 ...

  8. DataTable转换为Model实体对象

    记得在学校的时候,接触得最多的就是SqlHelper,每次在读取的时候不管是DataTable还是DataReader转换为实体对象的时候是最恼火的,因为要写很多代码,而且没有什么意义.后面接触到了反 ...

  9. 将DataTable转换为List<T>对象遇到问题:类型“System.Int64”的对象无法转换为类型“System.Int32”。

    可以利用反射将DataTable转换为List<T>对象:原始链接http://www.jb51.net/article/67386.htm 但是该方法在DataTable里某个字段类型是 ...

随机推荐

  1. JQuery.validate.js 表单验证

    官方网站:http://bassistance.de/jquery-plugins/jquery-plugin-validation/API: http://jquery.bassistance.de ...

  2. 【校验】TCP和UDP的校验和

    一开始,私以为校验和只是简单的求和得到的结果,后来在TCP和UDP里面看到使用的校验和方式有点奇怪--二进制反码(循环进位)求和. 人类的认知过程必将从简单到复杂,看下这个二进制反码循环求和是啥子意思 ...

  3. docker学习之二镜像创建

    继上一篇docker入门之后写一点使用的经验. 通过命令:docker run -it REPOSITORY或IMAGE ID   注:-it后面跟的字段可以通过下面指令获得 创建运行的容器,会进入一 ...

  4. 【原】iOS学习之tableView的常见BUG

    1.TableView头视图不随视图移动,头视图出现错位 错误原因:tableView的 UITableViewStyle 没有明确的声明 解决方法:在tableView声明的时候明确为 UITabl ...

  5. android 5.0以上通知栏、状态栏图标变成白色

    在5.0以上的系统上发现,平常的自定义notification出来的icon,居然在状态栏上变成了纯白色的icon. 看源代码会发现: protected void applyColorsAndBac ...

  6. ACCEPTANCE CRITERIA FOR USER STORIES

    One of the teams I have recently coached quickly got a grasp of how to phrase user stories but found ...

  7. CSS Reset

    html, body, div, span, applet, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote, pre,a, abbr, ac ...

  8. iOS程序模块化设计

    一.模块化设计的概述: 模块化设计(Block-based design):对一定范围内的不同功能或相同功能的不同性能.不同规格的产品进行功能分析的基础上,划分并设计出一系列功能模块,通过模块的选择和 ...

  9. C/C++编译和链接过程详解 (重定向表,导出符号表,未解决符号表)

    详解link  有 些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错 ...

  10. TestNG Assert 详解

    org.testng.Assert 用来校验接口测试的结果,那么它提供哪些方法呢? 中心为Assert测试类,一级节点为方法例如assertEquals,二级结点为参数类型及参数个数,double 3 ...