C#发现之旅第十二讲 基于反射和动态编译的快速ORM框架(下)
对于字符串类型的属性,其默认值就是“DBNull”。而对于其他的整数或者日 期类型的属性,并没有默认值,因此是无条件的插入到数据库中。
我们使用以下的代 码来生成上述代码文本
myWriter.WriteLine("public override int FillInsertCommand( System.Data.IDbCommand cmd , object objRecord )");
myWriter.BeginGroup("{");
myWriter.WriteLine("if( cmd == null ) throw new ArgumentNullException(\"cmd\");");
myWriter.WriteLine("if( objRecord == null ) throw new ArgumentNullException(\"objRecord\");");
myWriter.WriteLine(RecordType.FullName + " myRecord = objRecord as " + RecordType.FullName + " ;");
myWriter.WriteLine("if( myRecord == null ) throw new ArgumentException(\"must type '" + RecordType.FullName + "' \");");
myWriter.WriteLine("System.Collections.ArrayList myFieldNames = new System.Collections.ArrayList();");
myWriter.WriteLine ("System.Collections.ArrayList myValues = new System.Collections.ArrayList ();");
for (int iCount = 0; iCount < ps.Length; iCount++)
{
System.Reflection.PropertyInfo p = ps[iCount];
if (p.CanRead == false)
{
continue;
}
BindFieldAttribute fa = (BindFieldAttribute) Attribute.GetCustomAttribute(
p, typeof (BindFieldAttribute));
string FieldName = GetBindFieldName(p);
myWriter.WriteLine("");
Type pt = p.PropertyType;
object DefaultValue = this.GetDefaultValue(p);
if (pt.Equals(typeof(string)))
{
myWriter.WriteLine("if( myRecord." + p.Name + " != null && myRecord." + p.Name + ".Length != 0 )");
myWriter.BeginGroup("{");
myWriter.WriteLine ("myFieldNames.Add( \"" + FieldName + "\" );");
myWriter.WriteLine("myValues.Add( myRecord." + p.Name + " );");
myWriter.EndGroup("}");
}
else if (pt.Equals(typeof(DateTime)))
{
myWriter.WriteLine("myFieldNames.Add( \"" + FieldName + "\" );");
if (fa.WriteFormat != null && fa.WriteFormat.Length > 0)
{
myWriter.WriteLine ("myValues.Add( myRecord." + p.Name + ".ToString(\"" + fa.WriteFormat + "\") );");
}
else
{
myWriter.WriteLine("myValues.Add( myRecord." + p.Name + ".ToString(\"yyyy-MM-dd HH:mm:ss\") );");
}
}
else
{
myWriter.WriteLine("myFieldNames.Add( \"" + FieldName + "\" );");
myWriter.WriteLine("myValues.Add( myRecord." + p.Name + " );");
}
}//for
myWriter.WriteLine("");
myWriter.WriteLine("if( myFieldNames.Count == 0 ) return 0 ;");
myWriter.WriteLine("cmd.Parameters.Clear() ;");
myWriter.WriteLine ("System.Text.StringBuilder mySQL = new System.Text.StringBuilder();");
myWriter.WriteLine("mySQL.Append( \"Insert Into " + TableName + " ( \" );");
myWriter.WriteLine("mySQL.Append( ConcatStrings( myFieldNames ));");
myWriter.WriteLine("mySQL.Append( \" ) Values ( \" );");
myWriter.WriteLine("for( int iCount = 0 ; iCount < myValues.Count ; iCount ++ )");
myWriter.BeginGroup("{");
myWriter.WriteLine("if( iCount > 0 ) mySQL.Append(\" , \" );");
if (bolNamedParameter)
{
myWriter.WriteLine("mySQL.Append(\" @Value\" + iCount ) ;");
myWriter.WriteLine("System.Data.IDbDataParameter parameter = cmd.CreateParameter();");
myWriter.WriteLine("parameter.Value = myValues[ iCount ] ;");
myWriter.WriteLine ("parameter.ParameterName = \"Value\" + iCount ;");
myWriter.WriteLine("cmd.Parameters.Add( parameter );");
}
else
{
myWriter.WriteLine("mySQL.Append(\" ? \") ;");
myWriter.WriteLine("System.Data.IDbDataParameter parameter = cmd.CreateParameter();");
myWriter.WriteLine("parameter.Value = myValues[ iCount ] ;");
myWriter.WriteLine("cmd.Parameters.Add( parameter );");
}
myWriter.EndGroup("}//for");
myWriter.WriteLine("mySQL.Append( \" ) \" );");
myWriter.WriteLine ("cmd.CommandText = mySQL.ToString();");
myWriter.WriteLine("return myValues.Count ;");
myWriter.EndGroup(")//public override int FillInsertCommand( System.Data.IDbCommand cmd , object objRecord )");
在这里我们首先输出检查参数的代码文本,然后遍历所有绑定字段的 属性对象,根据属性的数据类型分为字符串样式,日期样式和其他样式。对于字符串样式则 需要输出判断是否为空的代码,对于日期样式则还要考虑BindFieldAttribute特性中指明的 数据保存样式,对于其他样式则没有任何判断,直接输出。
生成删除数据的代码
基础类型RecordORMHelper预留了FillDeleteCommand函数,代码生成器自动生成代码 来实现FillDeleteCommand函数,而ORM框架就会创建一个数据库命令对象,然后调用 FillDeleteCommand函数来为删除数据而初始化数据库命令对象,然后执行SQL命令删除数据 。
在DB_Employees中使用一下代码来定义EmployeeID属性的。
///<summary>
/// 字段值 EmployeeID
///</summary>
private System.Int32 m_EmployeeID = 0 ;
///<summary>
/// 字段值 EmployeeID
///</summary>
[BindField("EmployeeID" , Key = true )]
public System.Int32 EmployeeID
{
get
{
return m_EmployeeID ;
}
set
{
m_EmployeeID = value;
}
}
附加的BindField特性中使用了“Key=true”指明了EmployeeID字段是关键字段。 于是我们很容易就想到使用SQL语句“Delete From Employees Where EmployeeID=指定 的员工编号”来删除数据。于是针对DB_Employees代码生成器生成的代码如下
public override int FillDeleteCommand( System.Data.IDbCommand cmd , object objRecord )
{
if( cmd == null ) throw new ArgumentNullException("cmd");
if( objRecord == null ) throw new ArgumentNullException("objRecord");
MyORM.DB_Employees myRecord = objRecord as MyORM.DB_Employees ;
if( myRecord == null ) throw new ArgumentException("must type 'MyORM.DB_Employees' ");
cmd.Parameters.Clear();
cmd.CommandText = @"Delete From Employees Where EmployeeID = ? " ;
System.Data.IDbDataParameter parameter = null ;
parameter = cmd.CreateParameter();
parameter.Value = myRecord.EmployeeID ;
cmd.Parameters.Add( parameter );
return 1 ;
}
我们可以使用以下代码来生成上述的C#代码 文本。
// 关键字段SQL参数名称列表
System.Collections.ArrayList KeyParameterNames = new System.Collections.ArrayList();
// 生成Where子 语句文本
System.Text.StringBuilder myWhereSQL = new System.Text.StringBuilder();
System.Collections.ArrayList KeyProperties = new System.Collections.ArrayList();
for (int iCount = 0; iCount < ps.Length; iCount++)
{
System.Reflection.PropertyInfo p = ps[iCount];
if (p.CanRead == false)
{
continue;
}
BindFieldAttribute fa = (BindFieldAttribute)Attribute.GetCustomAttribute(
p, typeof(BindFieldAttribute));
if (fa.Key == false)
{
continue;
}
string FieldName = this.GetBindFieldName(p);
if (myWhereSQL.Length > 0)
{
myWhereSQL.Append(" and ");
}
KeyProperties.Add(p);
if (bolNamedParameter)
{
string pName = "Key" + p.Name;
KeyParameterNames.Add(pName);
myWhereSQL.Append (FixFieldName(FieldName) + " = @" + pName + " ");
}
else
{
myWhereSQL.Append(FixFieldName (FieldName) + " = ? ");
}
}//for
myWriter.WriteLine("public override int FillDeleteCommand( System.Data.IDbCommand cmd , object objRecord )");
myWriter.BeginGroup ("{");
if (KeyProperties.Count == 0)
{
myWriter.WriteLine("throw new NotSupportedException (\"FillDeleteCommand\");");
}
else
{
myWriter.WriteLine("if( cmd == null ) throw new ArgumentNullException (\"cmd\");");
myWriter.WriteLine("if( objRecord == null ) throw new ArgumentNullException(\"objRecord\");");
myWriter.WriteLine (RecordType.FullName + " myRecord = objRecord as " + RecordType.FullName + " ;");
myWriter.WriteLine("if( myRecord == null ) throw new ArgumentException(\"must type '" + RecordType.FullName + "' \");");
System.Text.StringBuilder myDeleteSQL = new System.Text.StringBuilder();
myDeleteSQL.Insert(0, "Delete From " + TableName + " Where " + myWhereSQL.ToString());
myWriter.WriteLine("cmd.Parameters.Clear();");
myWriter.WriteLine ("cmd.CommandText = @\"" + myDeleteSQL.ToString() + "\" ;");
myWriter.WriteLine("System.Data.IDbDataParameter parameter = null ;");
int index = 0;
foreach (System.Reflection.PropertyInfo p in KeyProperties)
{
myWriter.WriteLine ("");
myWriter.WriteLine("parameter = cmd.CreateParameter ();");
WriteSetParameterValue(p, myWriter);
if (bolNamedParameter)
{
myWriter.WriteLine("parameter.ParameterName = \"" + KeyParameterNames [index] + "\";");
}
myWriter.WriteLine("cmd.Parameters.Add( parameter );");
index++;
}
myWriter.WriteLine("");
myWriter.WriteLine("return " + KeyProperties.Count + " ;");
}
myWriter.EndGroup(")");
在这段代码中,首先是遍历实体类型中所 有的绑定到字段的属性,根据其附加的BindFieldAttribute特性的Key值找到所有的关键字段 属性对象,并创建了一个“关键字段名1=@Key属性名1 And 关键字段名2=@Key属性名 2”(若未启用命名参数则为“关键字段名1=? And 关键字段名2=? ……”)格式的字符串,该字符串就是SQL语句的Where子语句了,若启用 命名参数则生成的文本为。
若没有找到任何关键属性,则无法确定删除记录使用的查 询条件,此时代码生成器就会输出抛出异常的代码。在这里代码生成器不会直接抛出异常而 导致ORM框架过早的报警,未来可能有开发人员定义的实体类型只是为了查询或者新增数据库 记录,那时不需要定义关键属性。若对这种实体类型过早的报警则减少了快速ORM框架的使用 范围。
若实体类型定义了一个或者多个关键属性,则开始输出代码文本,首先输出检 查参数的代码文本,然后遍历所有的关键属性对象,生成向数据库命令对象添加参数的代码 。
这里还用到了一个WriteSetParamterValue的方法用于书写设置参数值的过程,其 代码为
private void WriteSetParameterValue( System.Reflection.PropertyInfo p , IndentTextWriter myWriter )
{
if (p.PropertyType.Equals(typeof(DateTime)))
{
BindFieldAttribute fa = (BindFieldAttribute) Attribute.GetCustomAttribute(
p, typeof (BindFieldAttribute));
if (fa.WriteFormat == null || fa.WriteFormat.Length == 0)
{
myWriter.WriteLine("parameter.Value = myRecord." + p.Name + ".ToString (\"yyyy-MM-dd HH:mm:ss\");");
}
else
{
myWriter.WriteLine ("parameter.Value = myRecord." + p.Name + ".ToString(\"" + fa.WriteFormat + "\");");
}
}
else if (p.PropertyType.Equals(typeof(string)))
{
myWriter.WriteLine("if( myRecord." + p.Name + " == null || myRecord." + p.Name + ".Length == 0 )");
myWriter.WriteLine(" parameter.Value = System.DBNull.Value ;");
myWriter.WriteLine("else");
myWriter.WriteLine(" parameter.Value = myRecord." + p.Name + " ;");
}
else
{
myWriter.WriteLine("parameter.Value = myRecord." + p.Name + " ;");
}
}
该方法内判 断若属性数据类型为时间型则设置输出的数据格式,若为字符串类型,则判断数据是否为空 ,若为空则设置参数值为DBNull。
生成更新数据的代码
基础类型 RecordORMHelper预留了FillUpdateCommand函数,快速ORM框架更新数据库时首先创建一个数 据库命令对象,然后调用FillUpdateCommand函数设置SQL语句,添加SQL参数,然后执行该命 令对象接口更新数据库记录。对于DB_Employees,其FillUpdateCommand函数的代码为
public override int FillUpdateCommand( System.Data.IDbCommand cmd , object objRecord )
{
if( cmd == null ) throw new ArgumentNullException("cmd");
if( objRecord == null ) throw new ArgumentNullException("objRecord");
MyORM.DB_Employees myRecord = objRecord as MyORM.DB_Employees ;
if( myRecord == null ) throw new ArgumentException("must type 'MyORM.DB_Employees' ");
cmd.CommandText = @"Update Employees Set EmployeeID = ? , LastName = ? , FirstName = ? , Title = ? , TitleOfCourtesy = ? , Address = ? , BirthDate = ? , City = ? , Country = ? , EducationalLevel = ? , EMail = ? , Extension = ? , Goal = ? , HireDate = ? , HomePage = ? , HomePhone = ? , Notes = ? , PostalCode = ? , Region = ? , ReportsTo = ? , Sex = ? Where EmployeeID = ? " ;
cmd.Parameters.Clear();
System.Data.IDbDataParameter parameter = null ;
parameter = cmd.CreateParameter();
parameter.Value = myRecord.EmployeeID ;
cmd.Parameters.Add( parameter );
parameter = cmd.CreateParameter();
parameter.Value = myRecord.BirthDate.ToString("yyyy-MM-dd");
cmd.Parameters.Add( parameter );
为其他属性值添加SQL参数对象。。。。。。
parameter = cmd.CreateParameter();
parameter.Value = myRecord.Sex ;
cmd.Parameters.Add( parameter );
//这 里为查询条件添加参数
parameter = cmd.CreateParameter();
parameter.Value = myRecord.EmployeeID ;
cmd.Parameters.Add( parameter );
return 22 ;
}
这段代码结构比 较简单,首先是对参数进行判断,然后设置SQL更新语句,然后将所有的属性的值依次添加到 SQL参数列表中,最后还为查询将EmployeeID值添加到SQL参数列表中。
在代码生成器 中生成FillUpdateCommand代码文本的代码为
myWriter.WriteLine("public override int FillUpdateCommand( System.Data.IDbCommand cmd , object objRecord )");
myWriter.BeginGroup("{");
if (KeyProperties.Count == 0)
{
myWriter.WriteLine("throw new NotSupportedException (\"FillUpdateCommand\");");
}
else
{
myWriter.WriteLine("if( cmd == null ) throw new ArgumentNullException (\"cmd\");");
myWriter.WriteLine("if( objRecord == null ) throw new ArgumentNullException(\"objRecord\");");
myWriter.WriteLine (RecordType.FullName + " myRecord = objRecord as " + RecordType.FullName + " ;");
myWriter.WriteLine("if( myRecord == null ) throw new ArgumentException(\"must type '" + RecordType.FullName + "' \");");
// 更新用SQL语句文本
System.Text.StringBuilder myUpdateSQL = new System.Text.StringBuilder();
// 所有的SQL参 数名称
System.Collections.ArrayList ParameterNames = new System.Collections.ArrayList();
foreach (System.Reflection.PropertyInfo p in ps)
{
if (p.CanRead == false)
{
continue;
}
string FieldName = this.GetBindFieldName(p);
if (myUpdateSQL.Length > 0)
{
myUpdateSQL.Append(" , ");
}
if (bolNamedParameter)
{
string pName = "Value" + p.Name;
ParameterNames.Add( pName );
myUpdateSQL.Append(FixFieldName(FieldName) + " = @" + pName);
}
else
{
myUpdateSQL.Append(FixFieldName(FieldName) + " = ? ");
}
}//foreach
ParameterNames.AddRange(KeyParameterNames);
myUpdateSQL.Insert(0, "Update " + FixTableName(TableName) + " Set ");
myUpdateSQL.Append(" Where " + myWhereSQL.ToString());
myWriter.WriteLine("");
myWriter.WriteLine("cmd.CommandText = @\"" + myUpdateSQL.ToString() + "\" ;");
myWriter.WriteLine ("cmd.Parameters.Clear();");
myWriter.WriteLine ("System.Data.IDbDataParameter parameter = null ;");
myWriter.WriteLine("");
System.Collections.ArrayList ps2 = new System.Collections.ArrayList();
ps2.AddRange(ps);
ps2.AddRange(KeyProperties);
foreach (System.Reflection.PropertyInfo p in ps2)
{
if (p.CanRead == false)
{
continue;
}
myWriter.WriteLine("");
myWriter.WriteLine("parameter = cmd.CreateParameter();");
WriteSetParameterValue(p, myWriter);
if (bolNamedParameter)
{
// 设置 SQL命令对象的名称
myWriter.WriteLine ("parameter.ParameterName = \"" + ParameterNames[0] + "\";");
ParameterNames.RemoveAt(0);
}
myWriter.WriteLine("cmd.Parameters.Add( parameter );");
}//foreach
myWriter.WriteLine("");
myWriter.WriteLine("return " + ps2.Count + " ;");
}//else
myWriter.EndGroup(")//public override int FillUpdateCommand( System.Data.IDbCommand cmd , object objRecord )");
这里的 KeyProperties,KeyParameterNames和myWhereSQL的值都在生成FillDeleteCommand时已经设 置好了,这里直接拿来用。若KeyProperties没有内容,说明实体类型没有指明绑定了关键字 段的属性,此时无法生成更新时的查询语句,于是输出抛出异常的C#代码文本。
我们 首先遍历实体类型中所有的绑定了字段的属性对象,拼凑出“Update TableName Set 字段名1=@Value属性名1 , 字段名2=@Value属性名2”(若未启用命名参数则输出为 “Update TableName Set 字段名1=? , 字段名2=?”)样式的SQL文本,然后加上 myWhereSQL中的查询条件文本,从而得出了完整的SQL语句,然后将其输出到代码文本中。
我们有一次遍历实体类型所有绑定了字段的属性对象,对于每一个属性输出添加SQL 参数对象的C#代码文本。此外还遍历KeyProperties来生成添加查询条件SQL参数的C#代码文 本。
函数最后返回添加的SQL参数个数的返回语句。
生成完整的C#源代码文本
在实现了生成读取数据,插入数据,删除数据和更新数据的代码文本的程序代码后, 我们就可以实现完整的生成C#代码文本的程序代码了,这些程序代码就是方法GenerateCode 的全部内容,其代码为
private string GenerateCode( string nsName , string strFileName , System.Collections.ArrayList RecordTypes )
{
// 开始创建代码
IndentTextWriter myWriter = new IndentTextWriter();
myWriter.WriteLine("using System;");
myWriter.WriteLine("using System.Data;");
myWriter.WriteLine ("namespace " + nsName);
myWriter.BeginGroup("{");
// 对每一个数据容器对象创建数据处理类的代码
foreach (Type RecordType in RecordTypes)
{
string TableName = RecordType.Name;
BindTableAttribute ta = (BindTableAttribute)Attribute.GetCustomAttribute(
RecordType, typeof(BindTableAttribute), false);
if (ta != null)
{
TableName = ta.Name;
}
if (TableName == null || TableName.Trim().Length == 0)
{
TableName = RecordType.Name;
}
TableName = TableName.Trim();
System.Reflection.PropertyInfo[] ps = this.GetBindProperties(RecordType);
myWriter.WriteLine("public class " + RecordType.Name + "ORMHelper : " + typeof(RecordORMHelper).FullName);
myWriter.BeginGroup("{");
myWriter.WriteLine("");
myWriter.WriteLine("///<summary>创建对象</summary>");
myWriter.WriteLine("public " + RecordType.Name + "ORMHelper(){}");
myWriter.WriteLine("");
生成 重载TableName的代码
生成重载RecordFieldNames的代码
生成重载 FillUpdateCommand的代码
生成重载FillDeleteCommand的代码
生成重载 FillInsertCommand的代码
生成重载InnerReadRecord的代码
}//foreach
myWriter.EndGroup("}//namespace");
// 若需要保存临时生成的C#代码到指定的文件
if (strFileName != null && strFileName.Length > 0)
{
myWriter.WriteFile(strFileName, System.Text.Encoding.GetEncoding(936));
}
return myWriter.ToString();
}
这个函数 的参数是生成的代码的名称空间的名称,保存代码文本的文件名和要处理的数据库实体对象 类型列表。在函数中首先创建一个myWriter的代码文本书写器,输出导入名称空间的代码文 本,输出命名空间的代码文本,然后遍历RecordTypes列表中的所有的实体对象类型,对每一 个实体对象类型输出一个定义类的C#代码文本,类名就是 类型名称+ORMHelper,该类继承自 RecordORMHelper类型。然后执行上述的生成TableName,RecordFieldNames, FillUpdateCommand,FillDelteCommand,FillInsertCommand和InnerReadRecord的C#代码文 本的过程,这样就完成了针对一个实体对象类型的C#代码的生成过程。
当代码生成器 完成工作后,内置的代码文本书写器myWriter中就包含了完整的C#代码文本。这个代码文本 中包含了多个从RecordORMHelper类型派生的数据库操作帮助类型。这样我们就可以随即展开 动态编译的操作了。
动态编译
在代码生成器成功的生成所有的C#源代码文本 后,我们就可以执行动态编译了,函数MyFastORMFramework.BuildHelpers就是实现动态编译 ,其代码为
private int BuildHelpers( string strFileName )
{
System.Collections.ArrayList RecordTypes = new System.Collections.ArrayList();
foreach( Type RecordType in myRecordHelpers.Keys )
{
if( myRecordHelpers[ RecordType ] == null )
{
RecordTypes.Add( RecordType );
}
}//foreach
if( RecordTypes.Count == 0 )
return 0 ;
// 开始创建代码
string nsName = "Temp" + System.Guid.NewGuid().ToString("N");
// 生成C#代码
string strSource = GenerateCode(nsName, strFileName , RecordTypes );
// 编译临时生成的C#代码
System.Collections.Specialized.StringCollection strReferences = new System.Collections.Specialized.StringCollection();
System.CodeDom.Compiler.CompilerParameters options = new System.CodeDom.Compiler.CompilerParameters();
options.GenerateExecutable = false;
options.GenerateInMemory = true ;
// 添加编译器使用的引用
System.Collections.ArrayList refs = new System.Collections.ArrayList();
foreach( Type t in RecordTypes )
{
refs.Add( t.Assembly.CodeBase );
}
refs.Add( this.GetType().Assembly.CodeBase );
refs.AddRange( new string[]{
"mscorlib.dll",
"System.dll" ,
"System.Data.dll" ,
});
for( int iCount = 0 ; iCount < refs.Count ; iCount ++ )
{
string strRef = ( string ) refs[ iCount ] ;
if( strRef.StartsWith("file:///"))
strRef = strRef.Substring( "file:///".Length );
if( options.ReferencedAssemblies.Contains( strRef ) == false )
{
options.ReferencedAssemblies.Add( strRef );
}
}
//string strSource = myWriter.ToString();
// 调用C#代码编译 器编译生成程序集
Microsoft.CSharp.CSharpCodeProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
// 若使 用微软.NET框架.1则调用ICodeCompiler
//System.CodeDom.Compiler.ICodeCompiler compiler = provider.CreateCompiler ();
//System.CodeDom.Compiler.CompilerResults result = compiler.CompileAssemblyFromSource( options , strSource );
// 若使用VS.NET2005或更新版本编译程序会在这里形成一个编译警告信息,
// 则可以将上面两行代码去掉而使用下面的代码
System.CodeDom.Compiler.CompilerResults result = provider.CompileAssemblyFromSource(options, strSource);
if( result.Errors.Count == 0 )
{
System.Reflection.Assembly asm = result.CompiledAssembly ;
myAssemblies.Add( asm );
// 创建内置的数据 库对象操作对象
foreach( Type RecordType in RecordTypes )
{
Type t = asm.GetType( nsName + "." + RecordType.Name + "ORMHelper" );
RecordORMHelper helper = ( RecordORMHelper ) System.Activator.CreateInstance( t );
myRecordHelpers[ RecordType ] = helper ;
System.Console.WriteLine("FastORM为\"" + RecordType.FullName + "\"创建操作帮 助对象");
}
}
else
{
System.Console.WriteLine("ORM框架动态编译错 误" );
foreach( string strLine in result.Output )
{
System.Console.WriteLine( strLine );
}
}
provider.Dispose();
return RecordTypes.Count ;
}
在本函数中,我们遍历实体Lexington注册列表,找到所有没有装备数据库操 作帮助器的实体类型,添加到RecordTypes列表中,然后调用GenerateCode函数生成C#代码。
我们确定编译过程要引用的程序集,Mscorlib.dll,System.dll,System.Data.dll 是基本的必不可少的引用,所有的参与动态编译的实体对象类型所在的程序集也得引用,快 速ORM框架本身所在的程序集也得引用。将所有的引用信息添加到options的 ReferencedAssemblies列表中,这里的options变量是编译使用的参数。然后我们使用 myWriter.ToString()获得代码生成器生成的C#源代码文本。我们创建一个 CSharpCodeProvider对象,准备编译了,对于微软.NET框架1.1和2.0其调用过程是不同的。 对于微软.NET框架1.1,其调用过程为
Microsoft.CSharp.CSharpCodeProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.ICodeCompiler compiler = provider.CreateCompiler ();
System.CodeDom.Compiler.CompilerResults result = compiler.CompileAssemblyFromSource( options , strSource );
而对 微软.NET框架2.0其调用过程为
Microsoft.CSharp.CSharpCodeProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.CompilerResults result = provider.CompileAssemblyFromSource(options, strSource);
这体现了微 软.NET框架1.1和2.0之间的差别。但微软.NET框架2.0是兼容1.1的,因此用于微软.NET1.1的 代码可以在微软.NET2.0下编译通过,但编译器会提示警告信息。
这里调用 CompileAssemblyFromSource实际上就是调用微软.NET框架中的基于命令行的C#程序编译器 csc.exe的封装。其内部会根据编译器参数options保存的信息生成命令行文本然后启动 csc.exe进程。然后将csc.exe的输出结果保存在CompilerResults对象中。
若一切顺 利,则使用CompilerResults.CompiledAssembly就能获得编译后生成的程序集,然后我们使 用反射操作,对每一个实体类型从动态编译生成的程序集中获得对应的数据库帮助器的类型 ,然后使用System.Activator.CreateInstance函数就能实例化一个数据库操作帮助器,将这 个帮助器放置在实体类型注册列表中等待下次选用。
操作数据库
我们使用动 态编译技术获得了数据库操作帮助器,现在我们就使用这些帮助器来实现高速的ORM操作。
查询数据 ReadObjects
快速ORM框架中,定义了一个ReadObjects的函数,用 于从数据库中读取数据并生成若干个实体对象,其代码为
public System.Collections.ArrayList ReadObjects( string strSQL , Type RecordType )
{
this.CheckConnection();
if( strSQL == null )
{
throw new ArgumentNullException("strSQL");
}
if( RecordType == null )
{
throw new ArgumentNullException("RecordType");
}
RecordORMHelper helper = this.GetHelper( RecordType );
using( System.Data.IDbCommand cmd = this.Connection.CreateCommand())
{
cmd.CommandText = strSQL ;
System.Data.IDataReader reader = cmd.ExecuteReader();
System.Collections.ArrayList list = helper.ReadRecords( reader , 0 );
reader.Close();
return list ;
}
}
private void CheckConnection()
{
if( myConnection == null )
{
throw new InvalidOperationException("Connection is null");
}
if( myConnection.State != System.Data.ConnectionState.Open )
{
throw new InvalidOperationException ("Connection is not opened");
}
}
这个函数的 参数是SQL查询语句和实体对象类型。在这个函数中,首先是调用CheckConnection函数来检 查数据库的连接状态,然后使用GetHelper函数获得对应的数据库操作帮助类,然后执行SQL 查询,获得一个数据库读取器,然后调用数据操作帮助类的ReadRecords获得一个列表,该列 表就包含了查询数据所得的实体对象。这个过程没有使用反射,执行速度非常快,使用这个 快速ORM框架,执行速度跟我们传统的手工编写代码创建实体对象的速度是一样的,但大大降 低了我们的开发工作量。
在快速ORM框架中,根据ReadObjects函数派生了ReadObject ,ReadAllObject等系列读取数据的函数,其原理都是一样的。
删除数据 DeleteObject
在快速ORM框架中定义了一个DeleteObject函数用于删除数据,其代码 为
public int DeleteObject( object RecordObject )
{
this.CheckConnection();
if( RecordObject == null )
{
throw new ArgumentNullException ("RecordObject");
}
RecordORMHelper helper = this.GetHelper( RecordObject.GetType() );
using( System.Data.IDbCommand cmd = this.Connection.CreateCommand())
{
if( helper.FillDeleteCommand( cmd , RecordObject ) > 0 )
{
return cmd.ExecuteNonQuery();
}
}
return 0 ;
}
这个函数的参数就是要删除的对象,在函数中,首先调 用GetHelper函数获得数据操作帮助器,然后创建一个数据库命令对象,调用帮助类的 FillDeleteCommand函数初始化数据库命令对象,然后执行该命令对象即可删除数据,过程简 单明了。ORM框架还定义了DeleteObjects函数用于删除多个实体对象,其原理和 DeleteObject函数一样。
更新数据 UpdateObject
快速ORM框架定义了 UpdateObject函数用于更新数据,其代码为
public int UpdateObject( object RecordObject )
{
this.CheckConnection();
if( RecordObject == null )
{
throw new ArgumentNullException("RecordObject");
}
RecordORMHelper helper = this.GetHelper( RecordObject.GetType());
using( System.Data.IDbCommand cmd = this.Connection.CreateCommand())
{
int fields = helper.FillUpdateCommand( cmd , RecordObject );
if( fields > 0 )
{
return cmd.ExecuteNonQuery ();
}
}
return 0 ;
}
过程很简单,首先使用GetHelepr函数获得数据库帮助器,然后调用它的 FillUpdateCommand函数来设置数据库命令对象,然后执行数据库命令对象即可完成删除数据 的操作。ORM框架还定义了 UpdateObjects函数用于更新多条数据库记录,其原理和 UpdateObject函数是一样的。
新增数据 InsertObject
快速ORM框架定义了 InsertObject函数用于新增数据库记录,其代码为
public int InsertObject( object RecordObject )
{
this.CheckConnection();
if( RecordObject == null )
{
throw new ArgumentNullException ("RecordObject");
}
RecordORMHelper helper = this.GetHelper( RecordObject.GetType());
using( System.Data.IDbCommand cmd = this.Connection.CreateCommand())
{
int fields = helper.FillInsertCommand( cmd , RecordObject );
if( fields > 0 )
{
return cmd.ExecuteNonQuery();
}
}//using
return 0 ;
}
这个函数也很简单,使用GetHelper获得数据库帮助器,调用帮助器的 FillInsertCommand函数设置数据库命令对象,然后执行它即可向数据库插入一条记录。另外 一个InsertObjects函数用于插入多条数据库记录,其原理是一样的。
使用ORM框架
在这里我们建立一个简单的WinForm程序来测试使用快速ORM框架。首先我们在一个 Access数据库中建立一个员工信息表,名称为Empolyees,并相应的定义了一个数据库实体类 型DB_Employees。然后画出一个窗口放置一些控件,编写一些代码,运行程序,其运行界面 为
该演示程序主要代码为
/// <summary>
/// 连接数 据库,创建快速ORM框架对象
/// </summary>
/// <returns>ORM 框架对象</returns>
private MyFastORMFramework CreateFramework()
{
System.Data.OleDb.OleDbConnection conn = new System.Data.OleDb.OleDbConnection();
conn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + System.IO.Path.Combine( System.Windows.Forms.Application.StartupPath , "demomdb.mdb" );
conn.Open();
return new MyFastORMFramework( conn );
}
// 刷新按钮事件处理
private void cmdRefresh_Click(object sender, System.EventArgs e)
{
using( MyFastORMFramework myWork = this.CreateFramework())
{
RefreshList( myWork );
}
}
// 用户名列表当前项 目改变事件处理
private void lstName_SelectedIndexChanged(object sender, System.EventArgs e)
{
DB_Employees obj = lstName.SelectedItem as DB_Employees ;
if( obj != null )
{
this.txtID.Text = obj.EmployeeID.ToString () ;
this.txtName.Text = obj.FullName ;
this.txtTitleOfCourtesy.Text = obj.TitleOfCourtesy ;
this.txtAddress.Text = obj.Address ;
this.txtNotes.Text = obj.Notes ;
}
else
{
this.txtID.Text = "";
this.txtName.Text = "";
this.txtTitleOfCourtesy.Text = "";
this.txtNotes.Text = "" ;
this.txtAddress.Text = "";
}
}
// 新增按钮事件 处理
private void cmdInsert_Click(object sender, System.EventArgs e)
{
try
{
using( dlgRecord dlg = new dlgRecord())
{
dlg.Employe = new DB_Employees();
if( dlg.ShowDialog( this ) == DialogResult.OK )
{
using( MyFastORMFramework myWork = this.CreateFramework())
{
if( myWork.InsertObject( dlg.Employe ) > 0 )
{
RefreshList( myWork );
}
}
}
}
}
catch( Exception ext )
{
MessageBox.Show( ext.ToString());
}
}
// 删除按钮事件处理
private void cmdDelete_Click(object sender, System.EventArgs e)
{
DB_Employees obj = this.lstName.SelectedItem as DB_Employees ;
if( obj != null )
{
if( MessageBox.Show(
this ,
"是否删除 " + obj.FullName + " 的纪录?",
"系统提示" ,
System.Windows.Forms.MessageBoxButtons.YesNo ) == DialogResult.Yes )
{
using( MyFastORMFramework myWork = this.CreateFramework())
{
myWork.DeleteObject( obj );
RefreshList( myWork );
}
}
}
}
// 刷新员工名称列表
private void RefreshList( MyFastORMFramework myWork )
{
object[] objs = myWork.ReadAllObjects(typeof( DB_Employees ));
System.Collections.ArrayList list = new ArrayList();
list.AddRange( objs );
this.lstName.DataSource = list ;
this.lstName.DisplayMember = "FullName";
}
// 修改按 钮事件处理
private void cmdEdit_Click(object sender, System.EventArgs e)
{
DB_Employees obj = this.lstName.SelectedItem as DB_Employees ;
if( obj == null )
return ;
using( dlgRecord dlg = new dlgRecord())
{
dlg.txtID.ReadOnly = true ;
dlg.Employe = obj ;
if( dlg.ShowDialog( this ) == DialogResult.OK )
{
using( MyFastORMFramework myWork = this.CreateFramework())
{
if( myWork.UpdateObject( obj ) > 0 )
{
RefreshList( myWork );
}
}
}
}
}
这段代码是 比较简单的,而实体类型DB_Employees的代码可以很容易的使用代码生成器生成出来。借助 于快速ORM框架,使得基本的数据库记录维护操作开发速度快,运行速度也快。
部署 快速ORM框架
这个快速ORM框架是轻量级的,你只需要将MyFastORMFramework.cs以及 BindTableAttribute和BindFieldAttribute的代码复制到你的C#工程即可,也可将它们编译 成一个DLL,供VB.NET等其他非C#的.NET工程使用。
小结
在本课程中,我们使 用反射和动态编译技术实现了一个快速ORM框架,详细学习了一个比较简单的动态编译技术的 完整实现。动态编译技术将自由灵活和运行速度结合在一起,是一个比较强大的软件开发技 术,综合利用反射和动态编译技术使得我们有可能打造灵活而高速的程序框架。