使用CodeDom提高ORM性能
下载本文代码:https://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反射机制。例如:
public class LWordEntity2
{3
/// <summary>4
/// 获取或设置留言 ID5
/// </summary>6
[DataColumn(ColumnName = "LWordUID")]7
public int LWordUID8
{9
// 
10
}11

12
/// <summary>13
/// 获取或设置发送用户14
/// </summary>15
[DataColumn(ColumnName = "PostUser")]16
public string PostUser17
{18
// 
19
}20

21
/// <summary>22
/// 获取或设置发送时间23
/// </summary>24
[DataColumn(ColumnName = "PostTime")]25
public DateTime PostTime26
{27
// 
28
}29

30
/// <summary>31
/// 获取或设置文本内容32
/// </summary>33
[DataColumn(ColumnName = "TextContent")]34
public string TextContent35
{36
// 
37
}38
}
DataColumn是自定义的属性类,代码并不复杂所以在这里也就省略了。接下来需要通过反射读取自定义属性,并赋值。代码如下:
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
try26
{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
else45
{46
// 将值设置到属性47
propInfo.SetValue(objEntity, objValue, null);48
}49
}50
}51

以上代码实现了读取数据源数据并向实体赋值的功能。但这样做速度非常慢,因为每读取一条数据库记录,每读取一个数据字段并向实体赋值的时候,都必须进行一次反射操作。数据量越大,且数据字段或实体属性越多,那么速度就越慢!在以上代码中,对实体的反射操作,其目的就是赋值。可以被等价的语句所替代:
entity.Prop = dr[“Porp”];
用简单的赋值语句肯定要比反射的速度快很多,而大数据量和多数据库字段对其影响也不是很大。不过需要注意的是因为每一个实体的具体属性不相同,所以赋值过程也是不相同的。例如:
News实体赋值代码:
void PutEntityProperties(NewsEntity entity, DbDataReader dr)2
{3
// 新闻 ID4
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实体赋值代码:
void PutEntityProperties(UserEntity entity, DbDataReader dr)2
{3
// 用户 ID4
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类用白色表示。具体代码如下:
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.Data9
{10
partial class SQLHelper11
{12
public void PutEntityProperties<T>(T entity, DbDataReader dr) where T : class13
{14
// 获取设置器15
IEntityPropertyPutter<T> putter = EntityPropertyPutterFactory.Create<T>(entity, this.IncludeDebugInformation);16

17
if (putter == null)18
throw new NullReferenceException(@"设置器为空( Null Putter )");19

20
try21
{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:
using System;2
using System.Collections;3
using System.Reflection;4

5
namespace Net.AfritXia.Data6
{7
/// <summary>8
/// 实体属性设置器工厂类9
/// </summary>10
internal sealed class EntityPropertyPutterFactory11
{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 : class23
{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
else43
{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:
#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 _Debug9
using System.IO;10
#endif11
using System.Reflection;12

13
using Microsoft.CSharp;14

15
using Net.AfritXia.Data.Mapping;16

17
namespace Net.AfritXia.Data18
{19
/// <summary>20
/// 构建实体属性设置器21
/// </summary>22
internal sealed class EntityPropertyPutterMaker23
{24
// 默认名称空间25
private const string DefaultNamespace = "Net.AfritXia.Data._AutoCode";26
// QuicklyPutter 类名称27
private const string QuicklyPutterClassName = "QuicklyPutter";28

29
// 包含调试信息30
private bool m_includeDebugInfo = false;31

32
类构造器40

41
/// <summary>42
/// 设置或获取是否包含调试信息43
/// </summary>44
public bool IncludeDebugInformation45
{46
set47
{48
this.m_includeDebugInfo = value;49
}50

51
get52
{53
return this.m_includeDebugInfo;54
}55
}56

57
/// <summary>58
/// 构建实体属性设置器59
/// </summary>60
/// <typeparam name="T">实体类型模版</typeparam>61
/// <returns></returns>62
public IEntityPropertyPutter<T> Make<T>() where T : class63
{64
// 创建一个可编译的单元65
CodeCompileUnit compileUnit = this.MakeCompileUnit();66
// 创建名称空间67
CodeNamespace namespace_code = this.MakeNamespace();68
// 定义类69
CodeTypeDeclaration class_code = this.MakeClass<T>();70
// 创建 PutEntityProperties 方法71
CodeMemberMethod method_code = this.MakeMethod<T>();72

73
// 添加方法到类74
class_code.Members.Add(method_code);75
// 添加类到名称空间76
namespace_code.Types.Add(class_code);77
// 添加名称空间到编译单元78
compileUnit.Namespaces.Add(namespace_code);79

80
// 创建 C# 编译器81
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");82
// 创建编译参数83
CompilerParameters options = new CompilerParameters();84

85
// 添加对 System.dll 的引用86
options.ReferencedAssemblies.Add("System.dll");87
// 添加对 System.Data.dll 的引用88
options.ReferencedAssemblies.Add("System.Data.dll");89
// 添加对该项目的引用90
options.ReferencedAssemblies.Add(this.GetType().Assembly.Location);91
// 添加对实体项目的引用92
options.ReferencedAssemblies.Add(typeof(T).Assembly.Location);93
// 只在内存中编译94
options.GenerateInMemory = true;95

96
#if _Debug97
string srcFilePath = null;98

99
srcFilePath = @"C:\{0}_{1}.cs";100
srcFilePath = String.Format(srcFilePath, typeof(T).Name, QuicklyPutterClassName);101

102
// 源文件输出流103
StreamWriter srcOutput = new StreamWriter(srcFilePath, false);104
// 写出源文件105
provider.GenerateCodeFromCompileUnit(compileUnit, srcOutput, new CodeGeneratorOptions());106

107
srcOutput.Flush();108
srcOutput.Close();109
#endif110

111
// 编译并获取编译结果112
CompilerResults compileResult = provider.CompileAssemblyFromDom(options, compileUnit);113

114
// 编译失败则抛出异常115
if (compileResult.NativeCompilerReturnValue != 0)116
throw new Exception("编译失败 ( Compile Failed )");117

118
// 创建设置器119
object putter = compileResult.CompiledAssembly.CreateInstance(DefaultNamespace + "." + QuicklyPutterClassName);120

121
return (IEntityPropertyPutter<T>)putter;122
}123

124
/// <summary>125
/// 构建可编译单元126
/// </summary>127
/// <returns></returns>128
private CodeCompileUnit MakeCompileUnit()129
{130
// 创建一个可编译的单元131
return new CodeCompileUnit();132
}133

134
/// <summary>135
/// 构建名称空间136
/// </summary>137
/// <returns></returns>138
private CodeNamespace MakeNamespace()139
{140
// 创建名称空间141
return new CodeNamespace(DefaultNamespace);142
}143

144
/// <summary>145
/// 构建 QuicklyPutter 类146
/// </summary>147
/// <returns></returns>148
private CodeTypeDeclaration MakeClass<T>() where T : class149
{150
// 定义 QuicklyPutter 类151
CodeTypeDeclaration class_code = new CodeTypeDeclaration(QuicklyPutterClassName);152

153
// 令该类实现 IEntityPropertyPutter<T> 接口154
class_code.BaseTypes.Add(typeof(IEntityPropertyPutter<T>));155

156
// 添加 EntityTypeName 属性157
class_code = this.MakeEntityTypeNameProperty<T>(class_code);158
// 添加 CurrentPropName 属性159
class_code = this.MakeCurrentPropNameProperty(class_code);160
// 添加 CurrentDBColName 属性161
class_code = this.MakeCurrentDBColNameProperty(class_code);162

163
return class_code;164
}165

166
/// <summary>167
/// 构建 EntityTypeName 属性168
/// </summary>169
/// <typeparam name="T">实体类型模版</typeparam>170
/// <param name="targetClass">目标代码</param>171
/// <returns></returns>172
private CodeTypeDeclaration MakeEntityTypeNameProperty<T>(CodeTypeDeclaration targetClass) where T : class173
{174
if (targetClass == null)175
throw new ArgumentNullException("targetClass");176

177
/* 178
* 以下代码将生成179
* 180
* public string EntityTypeName181
* {182
* get183
* {184
* return 实体类型名称字符串185
* }186
* }187
* 188
* 189
*/190

191
// EntityTypeName 属性192
CodeMemberProperty entityTypeNameProp_code = null;193
194
// 创建属性195
entityTypeNameProp_code = new CodeMemberProperty();196
// 定义为公共属性197
entityTypeNameProp_code.Attributes = MemberAttributes.Public;198
// 返回字符串类型199
entityTypeNameProp_code.Type = new CodeTypeReference(typeof(string));200
// 属性名称201
entityTypeNameProp_code.Name = "EntityTypeName";202
// 返回语句203
entityTypeNameProp_code.GetStatements.Add(204
new CodeMethodReturnStatement(new CodePrimitiveExpression(typeof(T).Name)));205

206
// 添加属性到类207
targetClass.Members.Add(entityTypeNameProp_code);208

209
return targetClass;210
}211

212
/// <summary>213
/// 构建 CurrentPropName 属性214
/// </summary>215
/// <param name="targetClass">目标类代码</param>216
/// <returns></returns>217
private CodeTypeDeclaration MakeCurrentPropNameProperty(CodeTypeDeclaration targetClass)218
{219
if (targetClass == null)220
throw new ArgumentNullException("targetClass");221

222
/* 223
* 以下代码将生成224
* 225
* private string m_currPropName;226
* 227
* public string CurrentPropName 228
* {229
* get230
* {231
* return this.m_currPropName;232
* }233
* }234
* 235
*/236

237
// 变量名称238
const string VaribleName = "m_currPropName";239

240
// m_currPropName241
CodeMemberField m_currPropName_code = null;242

243
// 创建字段244
m_currPropName_code = new CodeMemberField();245
// 定义为私有成员246
m_currPropName_code.Attributes = MemberAttributes.Private;247
// 创建变量248
m_currPropName_code = new CodeMemberField(typeof(string), VaribleName);249

250
// 添加成员到类251
targetClass.Members.Add(m_currPropName_code);252

253
// CurrentPropName254
CodeMemberProperty currPropName_code = null;255
256
// 创建属性257
currPropName_code = new CodeMemberProperty();258
// 定义为公共属性259
currPropName_code.Attributes = MemberAttributes.Public;260
// 返回字符串类型261
currPropName_code.Type = new CodeTypeReference(typeof(string));262
// 属性名称263
currPropName_code.Name = "CurrentPropName";264
// get 返回语句265
currPropName_code.GetStatements.Add(266
new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), VaribleName)));267

268
// 添加属性到类269
targetClass.Members.Add(currPropName_code);270

271
return targetClass;272
}273

274
/// <summary>275
/// 构建 CurrentDBColName 属性276
/// </summary>277
/// <param name="targetClass">父级类</param>278
/// <returns></returns>279
private CodeTypeDeclaration MakeCurrentDBColNameProperty(CodeTypeDeclaration targetClass)280
{281
if (targetClass == null)282
throw new ArgumentNullException("targetClass");283

284
/* 285
* 以下代码将生成286
* 287
* private string m_currDBColName;288
* 289
* public string CurrentDBColName 290
* {291
* get292
* {293
* return this.m_currDBColName;294
* }295
* }296
* 297
*/298

299
// 变量名称300
const string VaribleName = "m_currDBColName";301
// m_currDBColName302
CodeMemberField m_currDBColName_code = null;303

304
// 创建字段305
m_currDBColName_code = new CodeMemberField();306
// 定义为私有成员307
m_currDBColName_code.Attributes = MemberAttributes.Private;308
// 创建变量309
m_currDBColName_code = new CodeMemberField(typeof(string), VaribleName);310

311
// 添加成员到类312
targetClass.Members.Add(m_currDBColName_code);313

314
// CurrentDBColName315
CodeMemberProperty currDBCol_code = null;316

317
// 创建属性318
currDBCol_code = new CodeMemberProperty();319
// 定义为公共属性320
currDBCol_code.Attributes = MemberAttributes.Public;321
// 返回字符串类型322
currDBCol_code.Type = new CodeTypeReference(typeof(string));323
// 属性名称324
currDBCol_code.Name = "CurrentDBColName";325
// get 返回语句326
currDBCol_code.GetStatements.Add(327
new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_currDBColName")));328

329
// 添加属性到类330
targetClass.Members.Add(currDBCol_code);331

332
return targetClass;333
}334

335
/// <summary>336
/// 构建 PutEntityProperties 方法337
/// </summary>338
/// <typeparam name="T"></typeparam>339
/// <param name="fromEntity"></param>340
/// <returns></returns>341
private CodeMemberMethod MakeMethod<T>() where T : class342
{343
// PutObjectProperties 方法344
CodeMemberMethod method_code = null;345
346
// 创建方法347
method_code = new CodeMemberMethod();348
// 定义为公共方法349
method_code.Attributes = MemberAttributes.Public;350
// 返回类型351
method_code.ReturnType = new CodeTypeReference(typeof(void));352
// 方法名称353
method_code.Name = "PutEntityProperties";354
// 添加参数 entity355
method_code.Parameters.Add(new CodeParameterDeclarationExpression(typeof(T), "entity"));356
// 添加参数 dr357
method_code.Parameters.Add(new CodeParameterDeclarationExpression(typeof(DbDataReader), "dr"));358

359
// 获取实体类型360
Type objType = typeof(T);361

362
// 获取 DataTable 属性标记363
object[] tabAttrList = objType.GetCustomAttributes(typeof(DataTableAttribute), false);364

365
if (tabAttrList == null || tabAttrList.Length <= 0)366
{367
throw new MappingException(368
String.Format(@"类 {0} 未标记 DataTable 属性 ( Unlabeled [DataTable] Attribute On Class {0} )", objType.Name));369
}370

371
// 获取属性信息372
PropertyInfo[] propInfoList = objType.GetProperties();373

374
if (propInfoList == null || propInfoList.Length <= 0)375
return null;376

377
foreach (PropertyInfo propInfo in propInfoList)378
{379
object[] colAttrList = propInfo.GetCustomAttributes(typeof(DataColumnAttribute), false);380

381
// 未标记 DataColumn 属性382
if (colAttrList == null || colAttrList.Length <= 0)383
continue;384

385
// 获取数据列属性386
DataColumnAttribute colAttr = colAttrList[0] as DataColumnAttribute;387

388
// 创建方法内容389
method_code = this.MakeMethodContent(method_code, propInfo, colAttr, this.IncludeDebugInformation);390
}391

392
return method_code;393
}394

395
/// <summary>396
/// 构建 PutEntityProperties 方法内容397
/// </summary>398
/// <param name="targetMethod"></param>399
/// <param name="prop"></param>400
/// <param name="attr"></param>401
/// <param name="includeDebugInfo"></param>402
/// <returns></returns>403
private CodeMemberMethod MakeMethodContent(CodeMemberMethod targetMethod, PropertyInfo prop, DataColumnAttribute attr, bool includeDebugInfo)404
{405
if (targetMethod == null)406
throw new ArgumentNullException("targetMethod");407

408
if (attr == null)409
throw new ArgumentNullException("attr");410

411
// 实体变量名称 entity412
string varEntityName = targetMethod.Parameters[0].Name;413
// 数据源变量名称 dr414
string varDrName = targetMethod.Parameters[1].Name;415

416
// entity 属性名称417
string varEntityPropName = String.Format(@"{0}.{1}", varEntityName, prop.Name);418
// dr 属性名称419
string varDrPropName = String.Format(@"{0}[""{1}""]", varDrName, attr.Name);420

421
// 创建变量422
CodeVariableReferenceExpression entityProp_code = new CodeVariableReferenceExpression(varEntityPropName);423
// 创建值424
CodeVariableReferenceExpression dr_code = new CodeVariableReferenceExpression(varDrPropName);425

426
// 包含调试信息427
if (includeDebugInfo)428
{429
// this.m_currPropName = entity.Prop430
targetMethod.Statements.Add(new CodeAssignStatement(431
new CodeVariableReferenceExpression("this.m_currPropName"),432
new CodePrimitiveExpression(prop.Name)));433

434
// this.m_currDBColName = attributeName435
targetMethod.Statements.Add(new CodeAssignStatement(436
new CodeVariableReferenceExpression("this.m_currDBColName"),437
new CodePrimitiveExpression(attr.Name)));438
}439

440
if (attr.IsNullable)441
{442
/* 443
* 以下代码生成的是条件判断代码444
* 445
* if (dr["
"] != DBNull.Value) {446
* entity.Prop = dr["
"];447
* }448
* 449
*/450

451
CodeConditionStatement if_code = new CodeConditionStatement();452

453
// if (dr["
"] != DBNull.Value)454
if_code.Condition = new CodeBinaryOperatorExpression(455
new CodeVariableReferenceExpression(varDrPropName),456
CodeBinaryOperatorType.IdentityInequality,457
new CodeVariableReferenceExpression("System.DBNull.Value"));458

459
// entity.Prop = dr["
"];460
if_code.TrueStatements.Add(new CodeAssignStatement(461
entityProp_code,462
new CodeCastExpression(prop.PropertyType, dr_code)));463

464
targetMethod.Statements.Add(if_code);465
}466
else467
{468
// entity.Prop = dr["
"];469
targetMethod.Statements.Add(new CodeAssignStatement(470
entityProp_code,471
new CodeCastExpression(prop.PropertyType, dr_code)));472
}473

474
return targetMethod;475
}476
}477
}代码时序图如下:

具体代码可以参考:
Net.AfritXia.Data/IEntityPropertyPutter.cs
Net.AfritXia.Data/EntityPropertyPutterFactory.cs
Net.AfritXia.Data/EntityPropertyPutterMaker.cs
TestProj/UnitTest_Putter.cs(可以运行该测试文件)
浙公网安备 33010602011771号