使用CodeDom提高ORM性能

下载本文代码: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读取数据的性能问题。

  ORMObject/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[0as 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, nullnull);
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

  NewsUser所具备的属性不同,所以赋值过程,也不相同!但毫无疑问,使用直接赋值的方法是速度最快的!试想一下,假如在做反射的时候不是直接赋值,而是根据自定义属性,动态的生成赋值代码,编译以后临时保存起来。那么以后再进行赋值操作的时候,直接调用这个编译好的赋值代码,不就可以大大提升程序性能了么?有没有一个办法可以自动建立类似上面这样的代码呢?我们可以考虑使用反射和CodeDom技术。




  首先为了解决不同实体的不同的赋值过程,我们需要建立一个接口:IEntityPropertyPutter。在该接口中的PutEntityProperties函数负责真正的赋值逻辑。在赋值的过程中会调用IEntityPropertyPutter的具体实现类的实例。具体类图如下:

 


  EntityPropertyPutterFactory工厂类负责创建IEntityPropertyPutter接口具体实现类的实例。首先该工厂类会从缓存中获取IEntityPropertyPutter接口实例,如果该实例为空(还没有被创建),那么该工厂类会调用EntityPropertyPutterMaker构建者类创建实例(Entity实体类本身也可以直接实现IEntityPropertyPutter接口,来加快程序的运行速度)。在构建者内部会动态创建新的程序集(Assembly),在该程序集中只存在一个QuicklyPutter类。在QuicklyPutter类中描述了具体的赋值逻辑,这些逻辑编码则是根据反射和CodeDom完成的。最后交由CodeDom动态编译……根据不同的实体,所创建的程序集也不相同。所编译成功的程序集是临时存放在内存里,所以QuicklyPutter类用白色表示。具体代码如下:

 1using System;
 2using System.Collections.Generic;
 3using System.Data.Common;
 4using System.Reflection;
 5
 6using Net.AfritXia.Data.Mapping;
 7
 8namespace 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:

 

 1using System;
 2using System.Collections;
 3using System.Reflection;
 4
 5namespace 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
  3using System;
  4using System.CodeDom;
  5using System.Collections.Specialized;
  6using System.CodeDom.Compiler;
  7using System.Data.Common;
  8#if _Debug
  9using System.IO;