说明
尽管随着NoSQL的普及,数据库访问的性能已经非常关注的重点了(可以通过架构来解决这个瓶颈),所以有越来越多的项目使用了ORM来访问和操作数据库,在周公的博客上有一个系列的文章来比较ADO.NET和一些常见的ORM,实际上现在周公业余研究的一个项目中,周公也使用了MyBatisNet(由iBatisNet升级而来)。不过仍然有使用ADO.NET的场合,如果使用ADO.NET则免不了要写大量的将DataTable或者DataReader转换成对应的实体类的代码,经过了大约24小时的编码和测试(非连续的,累计的),周公尝试写了一个辅助工具,它可以将DataTable或者DataReader中的数据自动转换成实体类,这个辅助工具只有两个类,一个是负责转换的类,另一个是Attribute类,用以标识实体类的非静态属性与数据集中的数据列的对应关系。
为了便于使用,将所有代码写在了一个文件里,代码中有详尽的注释,所以在这里就不再介绍其原理和如何实现的了。完整的代码如下:
- using System;
- using System.Collections.Generic;
- using System.Data;
- using System.Data.Common;
- using System.Reflection;
- ///
- /// 实体阅读器类,可以从DataTable中或者DbDataReader的实例中将数据转换成对应的示例
- /// 作者:周公
- /// 日期:2011-07-17
- /// 修改日期:2011-07-21
- /// 博客地址:http://blog.csdn.net/zhoufoxcn 或http://zhoufoxcn.blog.51cto.com
- /// 说明:(1)任何人都可以免费使用,请尽量保持此段说明。
- /// (2)这个版本还不是最终版本,有任何意见或建议请到http://weibo.com/zhoufoxcn处留言。
- ///
- public sealed class EntityReader
- {
- private const BindingFlags BindingFlag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
- //将类型与该类型所有的可写且未被忽略属性之间建立映射
- private static Dictionary
string, PropertyInfo>> propertyMappings = new Dictionary string, PropertyInfo>>(); - //存储Nullable
与T的对应关系 - private static Dictionary
genericTypeMappings = new Dictionary(); - static EntityReader()
- {
- 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));
- }
- ///
- /// 将DataTable中的所有数据转换成List>T<集合
- ///
- ///
DataTable中每条数据可以转换的数据类型 - /// 包含有可以转换成数据类型T的数据集合
- ///
- public static List
GetEntities new()(DataTable dataTable) where T : - {
- if (dataTable == null)
- {
- throw new ArgumentNullException("dataTable");
- }
- //如果T的类型满足以下条件:字符串、ValueType或者是Nullable
- if(typeof(T)==typeof(string)||typeof(T).IsValueType)
- {
- return GetSimpleEntities
(dataTable); - }
- else
- {
- return GetComplexEntities
(dataTable); - }
- }
- ///
- /// 将DbDataReader中的所有数据转换成List>T<集合
- ///
- ///
DbDataReader中每条数据可以转换的数据类型 - /// 包含有可以转换成数据类型T的DbDataReader实例
- ///
- public static List
GetEntities new()(DbDataReader reader) where T : - {
- List
list = new List(); - if (reader == null)
- {
- throw new ArgumentNullException("reader");
- }
- //如果T的类型满足以下条件:字符串、ValueType或者是Nullable
- if (typeof(T) == typeof(string) || typeof(T).IsValueType)
- {
- return GetSimpleEntities
(reader); - }
- else
- {
- return GetComplexEntities
(reader); - }
- }
- ///
- /// 从DataTable中将每一行的第一列转换成T类型的数据
- ///
- ///
要转换的目标数据类型 - /// 包含有可以转换成数据类型T的数据集合
- ///
- private static List
GetSimpleEntities new()(DataTable dataTable) where T : - {
- List
list = new List(); - foreach (DataRow row in dataTable.Rows)
- {
- list.Add((T)GetValueFromObject(row[0], typeof(T)));
- }
- return list;
- }
- ///
- /// 将指定的 Object 的值转换为指定类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- /// 要转换的目标数据类型
- ///
- 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);
- }
- }
- ///
- /// 从DataTable中读取复杂数据类型集合
- ///
- ///
要转换的目标数据类型 - /// 包含有可以转换成数据类型T的数据集合
- ///
- private static List
GetComplexEntities new()(DataTable dataTable) where T : - {
- if (!propertyMappings.ContainsKey(typeof(T)))
- {
- GenerateTypePropertyMapping(typeof(T));
- }
- List
list = new List(); - Dictionary<string, PropertyInfo> properties = propertyMappings[typeof(T)];
- //Dictionary
propertyColumnOrdinalMapping = GetPropertyColumnIndexMapping(dataTable.Columns, properties); - T t;
- foreach (DataRow row in dataTable.Rows)
- {
- t = new T();
- foreach (KeyValuePair<string, PropertyInfo> item in properties)
- {
- //int ordinal = -1;
- //if (propertyColumnOrdinalMapping.TryGetValue(item.Key, out ordinal))
- //{
- // item.Value.SetValue(t, GetValueFromObject(row[ordinal], item.Value.PropertyType), null);
- //}
- item.Value.SetValue(t, GetValueFromObject(row[item.Key], item.Value.PropertyType), null);
- }
- list.Add(t);
- }
- return list;
- }
- ///
- /// 从DbDataReader的实例中读取复杂的数据类型
- ///
- ///
要转换的目标类 - /// DbDataReader的实例
- ///
- private static List
GetComplexEntities new()(DbDataReader reader) where T : - {
- if (!propertyMappings.ContainsKey(typeof(T)))//检查当前是否已经有该类与类的可写属性之间的映射
- {
- GenerateTypePropertyMapping(typeof(T));
- }
- List
list = new List(); - Dictionary<string, PropertyInfo> properties = propertyMappings[typeof(T)];
- //Dictionary
propertyColumnOrdinalMapping = GetPropertyColumnIndexMapping(reader, properties); - T t;
- while (reader.Read())
- {
- t = new T();
- foreach (KeyValuePair<string, PropertyInfo> item in properties)
- {
- //int ordinal = -1;
- //if (propertyColumnOrdinalMapping.TryGetValue(item.Key, out ordinal))
- //{
- // item.Value.SetValue(t, GetValueFromObject(reader[ordinal], item.Value.PropertyType), null);
- //}
- item.Value.SetValue(t, GetValueFromObject(reader[item.Key], item.Value.PropertyType), null);
- }
- list.Add(t);
- }
- return list;
- }
- ///
- /// 从DbDataReader的实例中读取简单数据类型(String,ValueType)
- ///
- ///
目标数据类型 - /// DbDataReader的实例
- ///
- private static List
GetSimpleEntities (DbDataReader reader) - {
- List
list = new List(); - while (reader.Read())
- {
- list.Add((T)GetValueFromObject(reader[0], typeof(T)));
- }
- return list;
- }
- ///
- /// 将Object转换成字符串类型
- ///
- /// object类型的实例
- ///
- private static object GetString(object value)
- {
- return Convert.ToString(value);
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- ///
- private static object GetEnum(object value, Type targetType)
- {
- return Enum.Parse(targetType, value.ToString());
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetBoolean(object value)
- {
- if (value is Boolean)
- {
- return value;
- }
- else
- {
- byte byteValue = (byte)GetByte(value);
- if (byteValue == 0)
- {
- return false;
- }
- else
- {
- return true;
- }
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetByte(object value)
- {
- if (value is Byte)
- {
- return value;
- }
- else
- {
- return byte.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetSByte(object value)
- {
- if (value is SByte)
- {
- return value;
- }
- else
- {
- return SByte.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetChar(object value)
- {
- if (value is Char)
- {
- return value;
- }
- else
- {
- return Char.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetGuid(object value)
- {
- if (value is Guid)
- {
- return value;
- }
- else
- {
- return new Guid(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetInt16(object value)
- {
- if (value is Int16)
- {
- return value;
- }
- else
- {
- return Int16.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetUInt16(object value)
- {
- if (value is UInt16)
- {
- return value;
- }
- else
- {
- return UInt16.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetInt32(object value)
- {
- if (value is Int32)
- {
- return value;
- }
- else
- {
- return Int32.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetUInt32(object value)
- {
- if (value is UInt32)
- {
- return value;
- }
- else
- {
- return UInt32.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetInt64(object value)
- {
- if (value is Int64)
- {
- return value;
- }
- else
- {
- return Int64.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetUInt64(object value)
- {
- if (value is UInt64)
- {
- return value;
- }
- else
- {
- return UInt64.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetSingle(object value)
- {
- if (value is Single)
- {
- return value;
- }
- else
- {
- return Single.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetDouble(object value)
- {
- if (value is Double)
- {
- return value;
- }
- else
- {
- return Double.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetDecimal(object value)
- {
- if (value is Decimal)
- {
- return value;
- }
- else
- {
- return Decimal.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetDateTime(object value)
- {
- if (value is DateTime)
- {
- return value;
- }
- else
- {
- return DateTime.Parse(value.ToString());
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定枚举类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- ///
- private static object GetTimeSpan(object value)
- {
- if (value is TimeSpan)
- {
- return value;
- }
- else
- {
- return TimeSpan.Parse(value.ToString());
- }
- }
- ///
- /// 将Object类型数据转换成对应的可空数值类型表示
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- /// 可空数值类型
- ///
- 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);
- }
- }
- ///
- /// 将指定的 Object 的值转换为指定类型的值。
- ///
- /// 实现 IConvertible 接口的 Object,或者为 null
- /// 目标对象的类型
- ///
- private static object GetNonGenericValueFromObject(object value, Type targetType)
- {
- if (targetType.IsEnum)//因为
- {
- return GetEnum(value, targetType);
- }
- else
- {
- switch (targetType.Name)
- {
- case "Byte": return GetByte(value);
- case "SByte": return GetSByte(value);
- case "Char": return GetChar(value);
- case "Boolean": return GetBoolean(value);
- case "Guid": return GetGuid(value);
- case "Int16": return GetInt16(value) ;
- case "UInt16": return GetUInt16(value);
- case "Int32": return GetInt32(value);
- case "UInt32": return GetUInt32(value);
- case "Int64": return GetInt64(value);
- case "UInt64": return GetUInt64(value);
- case "Single": return GetSingle(value);
- case "Double": return GetDouble(value);
- case "Decimal": return GetDecimal(value);
- case "DateTime": return GetDateTime(value);
- case "TimeSpan": return GetTimeSpan(value);
- default: return null;
- }
- }
- }
- ///
- /// 获取该类型中属性与数据库字段的对应关系映射
- ///
- ///
- 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)
- {
- ignorable = false;
- 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);
- }
- }
- //private static Dictionary
GetPropertyColumnIndexMapping(DataColumnCollection dataSource, Dictionary properties) - //{
- // Stopwatch watch = new Stopwatch();
- // watch.Start();
- // Dictionary
propertyColumnIndexMapping=new Dictionary (dataSource.Count); - // foreach(KeyValuePair
item in properties) - // {
- // for (int i = 0; i < dataSource.Count; i++)
- // {
- // if (item.Key.Equals(dataSource[i].ColumnName, StringComparison.InvariantCultureIgnoreCase))
- // {
- // propertyColumnIndexMapping.Add(item.Key, i);
- // break;
- // }
- // }
- // }
- // watch.Stop();
- // Debug.WriteLine("Elapsed:" + watch.ElapsedMilliseconds);
- // return propertyColumnIndexMapping;
- //}
- //private static Dictionary
GetPropertyColumnIndexMapping(DbDataReader dataSource, Dictionary properties) - //{
- // Dictionary
propertyColumnIndexMapping = new Dictionary (dataSource.FieldCount); - // foreach (KeyValuePair
item in properties) - // {
- // for (int i = 0; i < dataSource.FieldCount; i++)
- // {
- // if (item.Key.Equals(dataSource.GetName(i), StringComparison.InvariantCultureIgnoreCase))
- // {
- // propertyColumnIndexMapping.Add(item.Key, i);
- // continue;
- // }
- // }
- // }
- // return propertyColumnIndexMapping;
- //}
- }
- ///
- /// 自定义属性,用于指示如何从DataTable或者DbDataReader中读取类的属性值
- ///
- public class ColumnNameAttribute : Attribute
- {
- ///
- /// 类属性对应的列名
- ///
- public string ColumnName { get; set; }
- ///
- /// 指示在从DataTable或者DbDataReader中读取类的属性时是否可以忽略这个属性
- ///
- public bool Ignorable { get; set; }
- ///
- /// 构造函数
- ///
- /// 类属性对应的列名
- public ColumnNameAttribute(string columnName)
- {
- ColumnName = columnName;
- Ignorable = false;
- }
- ///
- /// 构造函数
- ///
- /// 指示在从DataTable或者DbDataReader中读取类的属性时是否可以忽略这个属性
- public ColumnNameAttribute(bool ignorable)
- {
- Ignorable = ignorable;
- }
- ///
- /// 构造函数
- ///
- /// 类属性对应的列名
- /// 指示在从DataTable或者DbDataReader中读取类的属性时是否可以忽略这个属性
- public ColumnNameAttribute(string columnName, bool ignorable)
- {
- ColumnName = columnName;
- Ignorable = ignorable;
- }
- }
用法举例
这里以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
list6 = EntityReader.GetEntities (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的例子,可以作为想学习这方面知识的朋友一个范例。除此之外,还可以将这个类用于中小型系统开发中。
如果大家有什么好的意见或建议,或者发现了bug,请在博客下方留言或者到http://weibo.com/zhoufoxcn上留言。
周公
2011-07-17