下载本文代码:http://files.cnblogs.com/afritxia2008/WebTest.rar(请使用 Visual Studio 2008 打开)
在进行讨论之前,我假设读者已经了解.NET反射、自定义属性、CodeDom这些技术。并接触过ORM框架源码,如果对ORM并不了解,可以参考:http://www.cnblogs.com/xdesigner/archive/2008/06/24/1228702.html。在这篇文章中,我们主要讨论通过CodeDom提高ORM读取数据的性能问题。
ORM(Object/Relation Mapping对象-关系数据库映射)其中的一个功能是将数据源数据赋值给实体。实现方法是利用自定义属性和.NET反射机制。例如:
1
public class LWordEntity
2
{
3
/**//// <summary>
4
/// 获取或设置留言 ID
5
/// </summary>
6
[DataColumn(ColumnName = "LWordUID")]
7
public int LWordUID
8
{
9
// 
10
}
11
12
/**//// <summary>
13
/// 获取或设置发送用户
14
/// </summary>
15
[DataColumn(ColumnName = "PostUser")]
16
public string PostUser
17
{
18
// 
19
}
20
21
/**//// <summary>
22
/// 获取或设置发送时间
23
/// </summary>
24
[DataColumn(ColumnName = "PostTime")]
25
public DateTime PostTime
26
{
27
// 
28
}
29
30
/**//// <summary>
31
/// 获取或设置文本内容
32
/// </summary>
33
[DataColumn(ColumnName = "TextContent")]
34
public string TextContent
35
{
36
// 
37
}
38
}
DataColumn是自定义的属性类,代码并不复杂所以在这里也就省略了。接下来需要通过反射读取自定义属性,并赋值。代码如下:
1
public void PutEntityProperties(object objEntity, DbDataReader dr)
2
{
3
// 获取实体类型
4
Type objType = objEntity.GetType();
5
6
// 获取属性信息
7
PropertyInfo[] propInfoList = objType.GetProperties();
8
9
if (propInfoList == null || propInfoList.Length <= 0)
10
return;
11
12
foreach (PropertyInfo propInfo in propInfoList)
13
{
14
object[] colAttrList = propInfo.GetCustomAttributes(typeof(DataColumnAttribute), false);
15
16
// 未标记 DataColumn 属性
17
if (colAttrList == null || colAttrList.Length <= 0)
18
continue;
19
20
// 获取数据列属性
21
DataColumnAttribute colAttr = colAttrList[0] as DataColumnAttribute;
22
23
int ordinal = -1;
24
25
try
26
{
27
// 获取数据列序号
28
ordinal = dr.GetOrdinal(colAttr.ColumnName);
29
}
30
catch (Exception ex)
31
{
32
throw new MappingException(
33
String.Format("{0} 未找到该数据列( Cannot Found this Column {0} )", colAttr.ColumnName), ex);
34
}
35
36
// 获取数据列值
37
object objValue = dr.GetValue(ordinal);
38
39
if (objValue is DBNull)
40
{
41
// 将 null 值设置到属性
42
propInfo.SetValue(objEntity, null, null);
43
}
44
else
45
{
46
// 将值设置到属性
47
propInfo.SetValue(objEntity, objValue, null);
48
}
49
}
50
}
51
以上代码实现了读取数据源数据并向实体赋值的功能。但这样做速度非常慢,因为每读取一条数据库记录,每读取一个数据字段并向实体赋值的时候,都必须进行一次反射操作。数据量越大,且数据字段或实体属性越多,那么速度就越慢!在以上代码中,对实体的反射操作,其目的就是赋值。可以被等价的语句所替代:
entity.Prop = dr[“Porp”];
用简单的赋值语句肯定要比反射的速度快很多,而大数据量和多数据库字段对其影响也不是很大。不过需要注意的是因为每一个实体的具体属性不相同,所以赋值过程也是不相同的。例如:
News实体赋值代码:
1
void PutEntityProperties(NewsEntity entity, DbDataReader dr)
2
{
3
// 新闻 ID
4
entity.ID = (int)dr["ID"];
5
// 标题
6
entity.Title = (string)dr["Title"];
7
// 摘要
8
entity.Summary = (string)dr["Summary"];
9
// 发送用户
10
entity.PostUser = (string)dr["PostUser"];
11
// 发送时间
12
entity.PostTime = (DateTime)dr["PostTime"];
13
// 文本内容
14
entity.TextContent = (string)dr["TextContent"];
15
}
User实体赋值代码:
1
void PutEntityProperties(UserEntity entity, DbDataReader dr)
2
{
3
// 用户 ID
4
entity.ID = (int)dr["ID"];
5
// 用户名称
6
entity.UserName = (string)dr["UserName"];
7
// 密码
8
entity.UserPass = (string)dr["UserPass"];
9
// 电子邮件
10
entity.EMail = (string)dr["EMail"];
11
// 注册时间
12
entity.RegisterTime = (DateTime)dr["RegisterTime"];
13
}
14
News与User所具备的属性不同,所以赋值过程,也不相同!但毫无疑问,使用直接赋值的方法是速度最快的!试想一下,假如在做反射的时候不是直接赋值,而是根据自定义属性,动态的生成赋值代码,编译以后临时保存起来。那么以后再进行赋值操作的时候,直接调用这个编译好的赋值代码,不就可以大大提升程序性能了么?有没有一个办法可以自动建立类似上面这样的代码呢?我们可以考虑使用反射和CodeDom技术。
首先为了解决不同实体的不同的赋值过程,我们需要建立一个接口:IEntityPropertyPutter。在该接口中的PutEntityProperties函数负责真正的赋值逻辑。在赋值的过程中会调用IEntityPropertyPutter的具体实现类的实例。具体类图如下:
EntityPropertyPutterFactory工厂类负责创建IEntityPropertyPutter接口具体实现类的实例。首先该工厂类会从缓存中获取IEntityPropertyPutter接口实例,如果该实例为空(还没有被创建),那么该工厂类会调用EntityPropertyPutterMaker构建者类创建实例(Entity实体类本身也可以直接实现IEntityPropertyPutter接口,来加快程序的运行速度)。在构建者内部会动态创建新的程序集(Assembly),在该程序集中只存在一个QuicklyPutter类。在QuicklyPutter类中描述了具体的赋值逻辑,这些逻辑编码则是根据反射和CodeDom完成的。最后交由CodeDom动态编译……根据不同的实体,所创建的程序集也不相同。所编译成功的程序集是临时存放在内存里,所以QuicklyPutter类用白色表示。具体代码如下:
1
using System;
2
using System.Collections.Generic;
3
using System.Data.Common;
4
using System.Reflection;
5
6
using Net.AfritXia.Data.Mapping;
7
8
namespace Net.AfritXia.Data
9

{
10
partial class SQLHelper
11
{
12
public void PutEntityProperties<T>(T entity, DbDataReader dr) where T : class
13
{
14
// 获取设置器
15
IEntityPropertyPutter<T> putter = EntityPropertyPutterFactory.Create<T>(entity, this.IncludeDebugInformation);
16
17
if (putter == null)
18
throw new NullReferenceException(@"设置器为空( Null Putter )");
19
20
try
21
{
22
// 设置实体属性
23
putter.PutEntityProperties(entity, dr);
24
}
25
catch (Exception ex)
26
{
27
string errorMessage = null;
28
29
// 定义异常信息格式
30
errorMessage = @"从数据库字段{0} 读取值并赋给属性{1} 时出错(实体类型: {2})";
31
// 格式化信息
32
errorMessage = String.Format(errorMessage, putter.CurrentDBColName, putter.CurrentPropName, putter.EntityTypeName);
33
34
// 抛出异常
35
throw new Exception(errorMessage, ex);
36
}
37
}
38
}
39
}
40
设置器工厂类EntityPropertyPutterFactory:
1
using System;
2
using System.Collections;
3
using System.Reflection;
4
5
namespace Net.AfritXia.Data
6

{
7
/**//// <summary>
8
/// 实体属性设置器工厂类
9
/// </summary>
10
internal sealed class EntityPropertyPutterFactory
11
{
12
// 设置器字典
13
private static readonly Hashtable g_putterHash = Hashtable.Synchronized(new Hashtable());
14
15
/**//// <summary>
16
/// 创建实体属性设置器
17
/// </summary>
18
/// <typeparam name="T">实体类型模版</typeparam>
19
/// <param name="fromEntity">实体</param>
20
/// <param name="includeDebugInfo">是否包含调试信息</param>
21
/// <returns></returns>
22
public static IEntityPropertyPutter<T> Create<T>(T fromEntity, bool includeDebugInfo) where T : class
23
{
24
if (fromEntity == null)
25
return null;
26
27
// 如果实体本身已经实现了 IEntityPropertyPutter<T> 接口,
28
// 则直接返回
29
if (fromEntity is IEntityPropertyPutter<T>)
30
return (IEntityPropertyPutter<T>)fromEntity;
31
32
IEntityPropertyPutter<T> putter = null;
33
34
// 获取字典关键字
35
string hashKey = fromEntity.GetType().FullName;
36
37
if (g_putterHash.ContainsKey(hashKey))
38
{
39
// 从字典中获取设置器
40
putter = g_putterHash[hashKey] as IEntityPropertyPutter<T>;
41
}
42
else
43
{
44
EntityPropertyPutterMaker maker = null;
45
46
// 创建构建器
47
maker = new EntityPropertyPutterMaker();
48
// 是否包含调试信息
49
maker.IncludeDebugInformation = includeDebugInfo;
50
51
// 新建应用程序集
52
putter = maker.Make<T>();
53
// 保存应用设置器到字典
54
g_putterHash.Add(hashKey, putter);
55
}
56
57
return putter;
58
}
59
}
60
}
构建器EntityPropertyPutterMaker:
1
#undef _Debug // 用于调试
2
3
using System;
4
using System.CodeDom;
5
using System.Collections.Specialized;
6
using System.CodeDom.Compiler;
7
using System.Data.Common;
8
#if _Debug
9
using System.IO;