再談使用Emit把Datatable轉換為對象集合(List )


一、前因和存在的問題

  前面我寫了一篇《使用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平台的想法,不妨關注一下,歡迎參與共同研究!如果覺得此文對你有所幫助,請高抬貴鼠,點一下推薦!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM