zoukankan      html  css  js  c++  java
  • 再谈使用Emit把Datatable转换为对象集合(List<T>)

    一、前因和存在的问题

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

    二、分析产生原因

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

     1  for (int index = 0; index < dt.Columns.Count; index++)
     2                 {
     3                     PropertyInfo propertyInfo = typeof(T).GetProperty(dt.Columns[index].ColumnName,StringComparison.CurrentCultureIgnoreCase);
     4                     Label endIfLabel = generator.DefineLabel();
     5                     if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
     6                     {
     7                         generator.Emit(OpCodes.Ldarg_0);
     8                         generator.Emit(OpCodes.Ldc_I4, index);
     9                         generator.Emit(OpCodes.Callvirt, isDBNullMethod);
    10                         generator.Emit(OpCodes.Brtrue, endIfLabel);
    11                         generator.Emit(OpCodes.Ldloc, result);
    12                         generator.Emit(OpCodes.Ldarg_0);
    13                         generator.Emit(OpCodes.Ldc_I4, index);
    14                         generator.Emit(OpCodes.Callvirt, getValueMethod);
    15                         generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
    16                         generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
    17                         generator.MarkLabel(endIfLabel);
    18                     }
    19                 }


    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 == 0)
                {
                    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 = 0; 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平台的想法,不妨关注一下,欢迎参与共同研究!如果觉得此文对你有所帮助,请高抬贵鼠,点一下推荐!

  • 相关阅读:
    leetcode 576. Out of Boundary Paths 、688. Knight Probability in Chessboard
    leetcode 129. Sum Root to Leaf Numbers
    leetcode 542. 01 Matrix 、663. Walls and Gates(lintcode) 、773. Sliding Puzzle 、803. Shortest Distance from All Buildings
    leetcode 402. Remove K Digits 、321. Create Maximum Number
    leetcode 139. Word Break 、140. Word Break II
    leetcode 329. Longest Increasing Path in a Matrix
    leetcode 334. Increasing Triplet Subsequence
    leetcode 403. Frog Jump
    android中webView加载H5,JS不能调用问题的解决
    通过nginx中转获取不到IP的问题解决
  • 原文地址:https://www.cnblogs.com/lindping/p/emitdatatolist.html
Copyright © 2011-2022 走看看