频道栏目
首页 > 资讯 > C# > 正文

用C#打造自己的实体转换器

11-07-19        来源:[db:作者]  
收藏   我要投稿

 

 

说明
尽管随着NoSQL的普及,数据库访问的性能已经非常关注的重点了(可以通过架构来解决这个瓶颈),所以有越来越多的项目使用了ORM来访问和操作数据库,在周公的博客上有一个系列的文章来比较ADO.NET和一些常见的ORM,实际上现在周公业余研究的一个项目中,周公也使用了MyBatisNet(由iBatisNet升级而来)。不过仍然有使用ADO.NET的场合,如果使用ADO.NET则免不了要写大量的将DataTable或者DataReader转换成对应的实体类的代码,经过了大约24小时的编码和测试(非连续的,累计的),周公尝试写了一个辅助工具,它可以将DataTable或者DataReader中的数据自动转换成实体类,这个辅助工具只有两个类,一个是负责转换的类,另一个是Attribute类,用以标识实体类的非静态属性与数据集中的数据列的对应关系。

为了便于使用,将所有代码写在了一个文件里,代码中有详尽的注释,所以在这里就不再介绍其原理和如何实现的了。完整的代码如下:

 

/// <summary>

/// 实体阅读器类,可以从DataTable中或者DbDataReader的实例中将数据转换成对应的示例

/// 作者:周公

/// 日期:2011-07-17

/// 博客地址:http://blog.csdn.net/zhoufoxcn 或http://zhoufoxcn.blog.51cto.com

/// 说明:(1)任何人都可以免费使用,请尽量保持此段说明。

///      (2)这个版本还不是最终版本,有任何意见或建议请到http://weibo.com/zhoufoxcn处留言。

/// </summary>

public sealed class EntityReader

{

    private const BindingFlags BindingFlag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    //将类型与该类型所有的可写且未被忽略属性之间建立映射

    private static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyMappings = new Dictionary<Type, Dictionary<string, PropertyInfo>>();

    //在数据库中常见的存储数据类型(String除外)

    private static Dictionary<Type, MethodInfo> simpleTypeMappings = new Dictionary<Type, MethodInfo>();

    //存储Nullable<T>与T的对应关系

    private static Dictionary<Type, Type> genericTypeMappings = new Dictionary<Type, Type>();

 

    static EntityReader()

    {

        //加载所有的数值类型及字符串类型

        simpleTypeMappings.Add(typeof(String), GetConvertMethod(typeof(String)));

        simpleTypeMappings.Add(typeof(Byte), GetConvertMethod(typeof(Byte)));

        simpleTypeMappings.Add(typeof(SByte), GetConvertMethod(typeof(Char)));

        simpleTypeMappings.Add(typeof(Char), GetConvertMethod(typeof(Boolean)));

        simpleTypeMappings.Add(typeof(Boolean), GetConvertMethod(typeof(Boolean)));

        simpleTypeMappings.Add(typeof(Guid), GetConvertMethod(typeof(Guid)));

        simpleTypeMappings.Add(typeof(Int16), GetConvertMethod(typeof(Int16)));

        simpleTypeMappings.Add(typeof(UInt16), GetConvertMethod(typeof(UInt16)));

        simpleTypeMappings.Add(typeof(Int32), GetConvertMethod(typeof(Int32)));

        simpleTypeMappings.Add(typeof(UInt32), GetConvertMethod(typeof(UInt32)));

        simpleTypeMappings.Add(typeof(Int64), GetConvertMethod(typeof(Int64)));

        simpleTypeMappings.Add(typeof(UInt64), GetConvertMethod(typeof(UInt64)));

        simpleTypeMappings.Add(typeof(Single), GetConvertMethod(typeof(Single)));

        simpleTypeMappings.Add(typeof(Double), GetConvertMethod(typeof(Double)));

        simpleTypeMappings.Add(typeof(Decimal), GetConvertMethod(typeof(Decimal)));

        simpleTypeMappings.Add(typeof(DateTime), GetConvertMethod(typeof(DateTime)));

        simpleTypeMappings.Add(typeof(TimeSpan), GetConvertMethod(typeof(TimeSpan)));

        simpleTypeMappings.Add(typeof(Enum), GetConvertMethod(typeof(Enum)));

 

        genericTypeMappings.Add(typeof(Byte?), typeof(Byte));

        genericTypeMappings.Add(typeof(SByte?), typeof(SByte));

        genericTypeMappings.Add(typeof(Char?), typeof(Char));

        genericTypeMappings.Add(typeof(Boolean?), typeof(Boolean));

        genericTypeMappings.Add(typeof(Guid?), typeof(Guid));

        genericTypeMappings.Add(typeof(Int16), typeof(Int16));

        genericTypeMappings.Add(typeof(UInt16), typeof(UInt16));

        genericTypeMappings.Add(typeof(Int32), typeof(Int32));

        genericTypeMappings.Add(typeof(UInt32), typeof(UInt32));

        genericTypeMappings.Add(typeof(Int64), typeof(Int64));

        genericTypeMappings.Add(typeof(UInt64), typeof(UInt64));

        genericTypeMappings.Add(typeof(Single), typeof(Single));

        genericTypeMappings.Add(typeof(Double), typeof(Double));

        genericTypeMappings.Add(typeof(Decimal), typeof(Decimal));

        genericTypeMappings.Add(typeof(DateTime), typeof(DateTime));

        genericTypeMappings.Add(typeof(TimeSpan), typeof(TimeSpan));

        genericTypeMappings.Add(typeof(Enum), typeof(Enum));

 

    }

    /// <summary>

    /// 将DataTable中的所有数据转换成List>T<集合

    /// </summary>

    /// <typeparam name="T">DataTable中每条数据可以转换的数据类型</typeparam>

    /// <param name="dataTable">包含有可以转换成数据类型T的数据集合</param>

    /// <returns></returns>

    public static List<T> GetEntities<T>(DataTable dataTable) where T : new()

    {

        if (dataTable == null)

        {

            throw new ArgumentNullException("dataTable");

        }

        //如果T的类型满足以下条件:字符串、ValueType或者是Nullable<ValueType>

        if(typeof(T)==typeof(string)||typeof(T).IsValueType)

        {

            return GetSimpleEntities<T>(dataTable);

        }

        else

        {

            return GetComplexEntities<T>(dataTable);

        }

    }

    /// <summary>

    /// 将DbDataReader中的所有数据转换成List>T<集合

    /// </summary>

    /// <typeparam name="T">DbDataReader中每条数据可以转换的数据类型</typeparam>

    /// <param name="dataTable">包含有可以转换成数据类型T的DbDataReader实例</param>

    /// <returns></returns>

    public static List<T> GetEntities<T>(DbDataReader reader) where T : new()

    {

        List<T> list = new List<T>();

        if (reader == null)

        {

            throw new ArgumentNullException("reader");

        }

        //如果T的类型满足以下条件:字符串、ValueType或者是Nullable<ValueType>

        if (typeof(T) == typeof(string) || typeof(T).IsValueType)

        {

            return GetSimpleEntities<T>(reader);

        }

        else

        {

            return GetComplexEntities<T>(reader);

        }

 

    }

    /// <summary>

    /// 从DataTable中将每一行的第一列转换成T类型的数据

    /// </summary>

    /// <typeparam name="T">要转换的目标数据类型</typeparam>

    /// <param name="dataTable">包含有可以转换成数据类型T的数据集合</param>

    /// <returns></returns>

    private static List<T> GetSimpleEntities<T>(DataTable dataTable) where T : new()

    {

        List<T> list = new List<T>();

        foreach (DataRow row in dataTable.Rows)

        {

            list.Add((T)GetValueFromObject(row[0], typeof(T)));

        }

        return list;

    }

    /// <summary>

    /// 将指定的Object 的值转换为指定类型的值。

    /// </summary>

    /// <param name="value">实现IConvertible 接口的Object,或者为null</param>

    /// <param name="targetType">要转换的目标数据类型</param>

    /// <returns></returns>

    private static object GetValueFromObject(object value, Type targetType)

    {

        if (targetType == typeof(string))//如果要将value转换成string类型

        {

            return GetString(value);

        }

        else if (targetType.IsGenericType)//如果目标类型是泛型

        {

            return GetGenericValueFromObject(value, targetType);

        }

        else//如果是基本数据类型(包括数值类型、枚举和Guid)

        {

            return GetNonGenericValueFromObject(value, targetType);

        }

    }

 

    /// <summary>

    /// 从DataTable中读取复杂数据类型集合

    /// </summary>

    /// <typeparam name="T">要转换的目标数据类型</typeparam>

    /// <param name="dataTable">包含有可以转换成数据类型T的数据集合</param>

    /// <returns></returns>

    private static List<T> GetComplexEntities<T>(DataTable dataTable) where T : new()

    {

        if (!propertyMappings.ContainsKey(typeof(T)))

        {

            GenerateTypePropertyMapping(typeof(T));

        }

        List<T> list = new List<T>();

        Dictionary<string, PropertyInfo> properties = propertyMappings[typeof(T)];

        foreach (DataRow row in dataTable.Rows)

        {

            T t = new T();

            foreach (KeyValuePair<string, PropertyInfo> item in properties)

            {

                //如果在DataTable的列中找不到与类属性名对应的列,则忽略

                if (row[item.Key] == null)

                {

                    continue;

                }

                else

                {

                    item.Value.SetValue(t, GetValueFromObject(row[item.Key], item.Value.PropertyType), null);

                }

            }

            list.Add(t);

        }

        return list;

    }

 

    /// <summary>

    /// 从DbDataReader的实例中读取复杂的数据类型

    /// </summary>

    /// <typeparam name="T">要转换的目标类</typeparam>

    /// <param name="reader">DbDataReader的实例</param>

    /// <returns></returns>

    private static List<T> GetComplexEntities<T>(DbDataReader reader) where T : new()

    {

        if (!propertyMappings.ContainsKey(typeof(T)))//检查当前是否已经有该类与类的可写属性之间的映射

        {

            GenerateTypePropertyMapping(typeof(T));

        }

        List<T> list = new List<T>();

        Dictionary<string, PropertyInfo> properties = propertyMappings[typeof(T)];

        T t = default(T);

        while (reader.Read())

        {

            t = new T();

            foreach (KeyValuePair<string, PropertyInfo> item in properties)

            {

                if (reader[item.Key] == null)//如果在reader的列中找不到与类属性名对应的列,则忽略

                {

                    continue;

                }

                else

                {

                    item.Value.SetValue(t, GetValueFromObject(reader[item.Key], item.Value.PropertyType), null);

                }

            }

            list.Add(t);

        }

        return list;

    }

    /// <summary>

    /// 从DbDataReader的实例中读取简单数据类型(String,ValueType)

    /// </summary>

    /// <typeparam name="T">目标数据类型</typeparam>

    /// <param name="reader">DbDataReader的实例</param>

    /// <returns></returns>

    private static List<T> GetSimpleEntities<T>(DbDataReader reader)

    {

        List<T> list = new List<T>();

        while (reader.Read())

        {

            list.Add((T)GetValueFromObject(reader[0], typeof(T)));

        }

        return list;

    }

    /// <summary>

    /// 将Object转换成字符串类型

    /// </summary>

    /// <param name="value">object类型的实例</param>

    /// <returns></returns>

    private static object GetString(object value)

    {

        return Convert.ToString(value);

    }

 

    /// <summary>

    /// 将Object类型数据转换成对应的可空数值类型表示

    /// </summary>

    /// <param name="value">实现IConvertible 接口的Object,或者为null</param>

    /// <param name="targetType">可空数值类型</param>

    /// <returns></returns>

    private static object GetGenericValueFromObject(object value,Type targetType)

    {

        if (value == DBNull.Value)

        {

            return null;

        }

        else

        {

            //获取可空数值类型对应的基本数值类型,如int?->int,long?->long

            Type nonGenericType= genericTypeMappings[targetType];

            return GetNonGenericValueFromObject(value, nonGenericType);

        }

    }

    /// <summary>

    /// 将指定的Object 的值转换为指定枚举类型的值。

    /// </summary>

    /// <param name="value">实现IConvertible 接口的Object,或者为null</param>

    /// <param name="targetType"></param>

    /// <returns></returns>

    private static object GetEnum(object value,Type targetType)

    {

        return Enum.Parse(targetType,value.ToString());

    }

 

    /// <summary>

    /// 将指定的Object 的值转换为指定类型的值。

    /// </summary>

    /// <param name="value">实现IConvertible 接口的Object,或者为null</param>

    /// <param name="targetType">目标对象的类型</param>

    /// <returns></returns>

    private static object GetNonGenericValueFromObject(object value, Type targetType)

    {

        if (targetType.IsEnum)//因为

        {

            return GetEnum(value, targetType);

        }

        else

        {

            switch (targetType.Name)

            {

                case "Byte": return GetValueFromObject<Byte>(value);

                case "SByte": return GetValueFromObject<SByte>(value);

                case "Char": return GetValueFromObject<Char>(value);

                case "Boolean": return GetValueFromObject<Boolean>(value);

                case "Guid": return GetValueFromObject<Guid>(value);

                case "Int16": return GetValueFromObject<Int16>(value);

                case "UInt16": return GetValueFromObject<UInt16>(value);

                case "Int32": return GetValueFromObject<Int32>(value);

                case "UInt32": return GetValueFromObject<UInt32>(value);

                case "Int64": return GetValueFromObject<Int64>(value);

                case "UInt64": return GetValueFromObject<UInt64>(value);

                case "Single": return GetValueFromObject<Single>(value);

                case "Double": return GetValueFromObject<Double>(value);

                case "Decimal": return GetValueFromObject<Decimal>(value);

                case "DateTime": return GetValueFromObject<DateTime>(value);

                case "TimeSpan": return GetValueFromObject<TimeSpan>(value);

                default: return null;

            }

        }

    }

 

    /// <summary>

    /// 将指定的Object 的值转换为指定类型的值。

    /// </summary>

    /// <typeparam name="T">期望转换的目标对象</typeparam>

    /// <param name="value">实现IConvertible 接口的Object,或者为null</param>

    /// <returns></returns>

    private static T GetValueFromObject<T>(object value) where T:new()

    {

        //如果可以obj是T的子类

        if (value is T)

        {

            return (T)value;

        }

        else if (typeof(T).IsEnum)

        {

            return (T)Enum.Parse(typeof(T), value.ToString());

        }

        else if (typeof(T) == typeof(Boolean))

        {

            object result = null;

            if (value.ToString() == "1")

            {

                result = true;

            }

            else

            {

                result = false;

            }

            return (T)result;

        }

        else if (typeof(T) == typeof(Guid))

        {

            ConstructorInfo constructorInfo = typeof(T).GetConstructor(new Type[] { typeof(string) });

            return (T)constructorInfo.Invoke(new object[] { value.ToString() });

        }

        else

        {

            MethodInfo method = simpleTypeMappings[typeof(T)];

            //调用该类型的转换方法来转换

            return (T)(method.Invoke(null, new object[] { value.ToString() }));

        }

    }

 

    /// <summary>

    /// 获取该类型中属性与数据库字段的对应关系映射

    /// </summary>

    /// <param name="type"></param>

    private static void GenerateTypePropertyMapping(Type type)

    {

        if (type != null)

        {

            PropertyInfo[] properties = type.GetProperties(BindingFlag);

            Dictionary<string, PropertyInfo> propertyColumnMapping = new Dictionary<string, PropertyInfo>(properties.Length);

            string description = string.Empty;

            Attribute[] attibutes = null;

            string columnName = string.Empty;

            bool ignorable = false;

            foreach (PropertyInfo p in properties)

            {

                columnName = string.Empty;

                attibutes = Attribute.GetCustomAttributes(p);

                foreach (Attribute attribute in attibutes)

                {

                    //检查是否设置了ColumnName属性

                    if (attribute.GetType() == typeof(ColumnNameAttribute))

                    {

                        columnName = ((ColumnNameAttribute)attribute).ColumnName;

                        ignorable = ((ColumnNameAttribute)attribute).Ignorable;

                        break;

                    }

                }

                //如果该属性是可读并且未被忽略的,则有可能在实例化该属性对应的类时用得上

                if (p.CanWrite&&!ignorable)

                {

                    //如果没有设置ColumnName属性,则直接将该属性名作为数据库字段的映射

                    if (string.IsNullOrEmpty(columnName))

                    {

                        columnName = p.Name;

                    }

                    propertyColumnMapping.Add(columnName, p);

                }

            }

            propertyMappings.Add(type, propertyColumnMapping);

        }

    }

   

    /// <summary>

    /// 根据对应的目标Type从Convert类中找到对应的转换方法

    /// </summary>

    /// <param name="targetType">对应的目标Type</param>

    /// <returns></returns>

    private static MethodInfo GetConvertMethod(Type targetType)

    {

        if (targetType == typeof(Enum) || targetType == typeof(Guid))

        {

            return null;

        }

        else if (targetType == typeof(Boolean))

        {

            return typeof(Convert).GetMethod("To" + targetType.Name, new Type[] { typeof(object) });

        }

        else if (targetType.IsValueType)//如果目标类型是除enum之外的值类型,调用该类型的Parse()方法

        {

            return targetType.GetMethod("Parse", new Type[] { typeof(string) });

        }

        else//如果目标类型是string,调用Convert.ToString()方法

        {

            return typeof(Convert).GetMethod("To" + targetType.Name, new Type[] { typeof(object) });

        }

    }

}

/// <summary>

/// 自定义属性,用于指示如何从DataTable或者DbDataReader中读取类的属性值

/// </summary>

public class ColumnNameAttribute : Attribute

{

    /// <summary>

    /// 类属性对应的列名

    /// </summary>

    public string ColumnName { get; set; }

    /// <summary>

    /// 指示在从DataTable或者DbDataReader中读取类的属性时是否可以忽略这个属性

    /// </summary>

    public bool Ignorable { get; set; }

    /// <summary>

    /// 构造函数

    /// </summary>

    /// <param name="columnName">类属性对应的列名</param>

    public ColumnNameAttribute(string columnName)

    {

        ColumnName = columnName;

        Ignorable = false;

    }

    /// <summary>

    /// 构造函数

    /// </summary>

    /// <param name="ignorable">指示在从DataTable或者DbDataReader中读取类的属性时是否可以忽略这个属性</param>

    public ColumnNameAttribute(bool ignorable)

    {

        Ignorable = ignorable;

    }

    /// <summary>

    /// 构造函数

    /// </summary>

    /// <param name="columnName">类属性对应的列名</param>

    /// <param name="ignorable">指示在从DataTable或者DbDataReader中读取类的属性时是否可以忽略这个属性</param>

    public ColumnNameAttribute(string columnName, bool ignorable)

    {

        ColumnName = columnName;

        Ignorable = ignorable;

    }

}


在本代码中所用到的实体类代码如下:

 

public class TY_ContentInfo

{

 

public int Id { get; set; }

 

public int ArticleId { get; set; }

 

public string Content { get; set; }

 

public DateTime CreateAt { get; set; }

 

public string ContentHash { get; set; }

 

[ColumnName(true)]

public bool IsVisible { get; set; }

}

用法举例


这里以MySQL为例(因为公司不允许安装破解和盗版软件,所以我的电脑上只有免费的SQLite和MySQL社区版了)。

 

MySqlConnection connection = new MySqlConnection("Server=localhost;Database=crawldb;Uid=root;Pwd=root;Port=3306;");

MySqlCommand command = new MySqlCommand("SELECT * FROM TY_Content order by Id desc limit 0,200000", connection);

MySqlDataAdapter adapter = new MySqlDataAdapter(command);

DataTable data6 = new DataTable();

adapter.Fill(data6);

Console.WriteLine("MySQL");

watch.Reset();

watch.Start();

List<TY_ContentInfo> list6 = EntityReader.GetEntities<TY_ContentInfo>(data6);

watch.Stop();

Console.WriteLine("Parse data in DataTable lasts ms:{0}", watch.ElapsedMilliseconds);

Console.WriteLine("Data record Count:{0}", list6.Count);

data6.Clear();

data6.Dispose();

list6.Clear();

list6 = null;

性能测试数据


在SQLite上有表如下:

 

CREATE TABLE TY_Content (

   Id            integer NOT NULL PRIMARY KEY UNIQUE,

   ArticleId            int                  not null,

   Content              text                 not null,

   ContentHash          varchar(32)          not null,

   CreateAt             datetime             null default CURRENT_TIMESTAMP

);

 

CREATE UNIQUE INDEX IDX_ContentHash on TY_Content (

ContentHash ASC

);里面共有数据128062条,采用DataTable和SQLiteDataReader的方式读取耗时分别为1582ms和11120ms。


在MySQL中也存在有上面结构的表,有数据175616条,采用DataTable和SQLiteDataReader的方式读取耗时分别为1958ms和9461ms。
在数据库记录条数百万级以下,使用它还可以可以的,实际上在真实的开发中不可能一次性读取数十万条数据的,因此还可以可用于一般的中小型网站。


未尽事宜
第一个未尽事宜就是我还在分析为什么从DataTable中读取比DataReader中读取要快,即使不同数据库都是如此,几乎相差一个数量级,这跟平时我们印象中“从DataReader中读数据要比DataTable中快”相冲突。
第二个未尽事宜是代码的优化问题,因为最近个人事情比较多,还有很多地方没有来得及优化,甚至我的另一种实现还没有比较,那就是采用分而治之的方式,针对每一种数据类型都写一种方法来处理,这样就可以去掉反射部分了,或许速度上还会有提高,但这还是我的预想,没有验证。


总结

上面是一个逻辑稍微有点复杂的例子,里面用到了反射、Attribute及ADO.NET的例子,可以作为想学习这方面知识的朋友一个范例。除此之外,还可以将这个类用于中小型系统开发中。

相关TAG标签
上一篇:WINODS下和LINUX下服务器级别的KILLORACLE进行方法
下一篇:【.Net MF网络开发板研究-04】Socket编程之服务端
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站