C#发现之旅第十一讲 使用反射和特性构造自己的ORM框架(上)
ORM背景
在数据库界,主流的数据库都是关系型数据库,其采用的关系型数据结构模型,无论从数学上还是实践中都相当的成熟,得到非常广泛的应用。在关系型数据结构理论中,所有的数据都组织成一个个相互独立的二维表格,一个数据表有若干行若干列。因此关系型数据库适合存储大量的结构简单的数据,不适合存储复杂的数据结构。
在编程界,面向对象的编程思想及其派生思想占据主流。面向对象的编程思想具有封装,继承,重载等手段来方便的实现比较复杂的数据结构,这适应了现代信息系统包含大量复杂数据结构的特点。因此面向对象的编程思想得到广泛应用。
关系型数据模型和面向对象的编程思想之间存在比较大的差别,数据在两者之间的交换是不大通畅的,就像南京,江北和主城区之间是长江来阻断交通。因此开发人员迫切需要破解这种数据交通的阻断。
以前程序员需要编程,从数据库中读取一个个字段值并赋值到编程对象的一个个字段或属性上,这就像在长江上架一个独木桥,开发效率低下,维护困难。后来出现一种叫ORM的框架性的程序,它能根据某些配置信息将数据库中的字段和编程对象的字段或属性之间建立映射关系,从而能方便的从数据库读取字段值并赋值到对象属性中。这是一种半自动的机制,能比较大的提高开发效率,简化维护,这就像在长江上架设了一座高速公路大桥。
从本质上说,关系型数据库和面向对象的编程思想之间的隔阂非常大,就像长江是天涧,即使建五六座大桥也不够用。彻底的解决方法就是抛弃关系型数据库而使用面向对象的数据库,不过这个过程就像南京江北整体搬迁到江南一样,工程浩大,时间漫长。在等待面向对象数据库的统治前,我们仍然得忍受关系型数据库和面向对象编程思想之间的数据交通不畅的痛苦,并使用ORM框架来很有限的降低这种痛苦。从这个角度上说,我们痛恨关系型数据库,就像搞运载火箭的人痛恨地球引力一样。
反射和特性
反射是.NET框架提供的一种高级编程接口。学过VB的都知道VB中有一个CallByName函数,它能调用对象的指定名称的成员方法,比如有个窗体对象,我们可以调用“frm.Close()”来关闭窗体,也可以调用“CallByName( frm , “Close”)”来实现同样的功能。而反射就是CallByName的.NET版本,而且功能更加强大。使用反射,我们可以列出任何对象类型的所有的字段,属性,方法和事件的名称,包括公开的或私有的。我们还可以更深入的获得字段的数据类型,成员方法的参数的个数,类型及其返回值;事件使用的委托类型等等。可以说反射技术就是.NET框架提供的只读的程序基因分析技术。
.NET框架对反射这种程序基因分析技术提供了天然的支持。在.NET框架中,任何对象类型都是从object类型上面派生的,object类型有一个GetType函数,该函数返回一个System.Type类型的对象,该对象就是反射操作的入口点,这样任何.NET对象类型都能用反射技术进行分析。
特性也是.NET框架提供的一种高级编程手段。它是附加在类型,字段,属性,函数等编程单元上面的额外信息,就相当于Access数据库中的表和字段的说明文本。它不会影响所附着的编程单元的正常执行。但它是客观存在的,可以通过反射来获得其信息,一般的我们可以调用System.Attribute类型的GetCustomAttribute函数来获得指定编程单元附加的指定类型的特性对象。
从编程角度看,特性也是一种对象类型,它们都是从System.Attribute上面派生的。.NET类库中已经定义了大量的特性类型,我们也可以定义自己的特性。
使用特性也很简单,也就是在编程单元前面使用方括号包含特性的定义,比如对于WinForm程序其入口函数具有以下类似的定义。在这里,函数定义前头的“[Ssystem.STAThread]”就表示这个函数附加了一个类型名为“System.STAThreadAttribute”的特性,这里存在一个C#的语法,在附加特性时可以将类型名称后面的“Attribute”后缀去掉,当然也可以写全名。一个编程单元可以附加多个特性。
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[System.STAThread]
static void Main()
{
Application.Run(new frmTestORM());
}
使用特性,我们可以在对象属性上附加数据库字段映射信息,使用反射,我们可以动态的读取和设置对象的属性值。这样特性和反射可以成为我们实行ORM的技术基础。
关于反射和特性的详细信息可以参考MSND中的相关文档。
ORM框架设计
我们首先来设计一个ORM框架。说到框架大家一定联想到.NET框架,J2EE框架。这些框架都是大公司劳苦数年才能完成,结构复杂功能强大。其实我们都可以根据各自需要自己开发一些通用的支持性质的软件,并美其名曰框架。现在我们就来设计一个轻量级的ORM框架,并应用我们今天要学习的反射和特性的.NET编程技术。
既然是轻量级的,我们就不考虑所有的情况,只考虑经常遇到的简单情况,复杂情况不予考虑。很多时候我们的编程对象和数据库表之间是存在简单的影射关系的,比如一个对象类型对应一个数据表,对象的一个属性对应数据表中的一个字段。此时我们可以定义两种特性,一个数据数据库表绑定特性,名为BindTableAttribute,用于将一个对象类型绑定到一个指定表名称的数据表上;还有一个数据库字段绑定特性,名为BindFieldAttribute,用于将一个对象属性绑定到一个指定名称的字段上面。
下图就是一个映射关系的例子,数据库中有个名为Employees的数据表,而开发者定义了DB_Employees类型。通过使用BindTableAttribute特性,将DB_Employess类型映射到数据表Employees,而是用BindFieldAttribute特性将DB_Employees的EmployeeID属性映射到数据库字段EmployeeID上面。类似的DB_Employees中的很多属性都映射到数据表Employees中的某个字段上,当然不是所有的对象类型的属性映射到数据库字段。通过在程序代码中,我们可以使用硬编码的方式将对象类型及其属性映射到数据库中的表和字段上面。
这种将映射信息保存在代码中的方式有利有弊,好处是程序代码比较集中,修改代码方便,坏处就是当数据库结构或者映射关系发生改变时,需要修改代码,这导致重新编译重新部署。一些ORM框架使用XML配置文件来保存对象和数据库的映射关系,不过这会导致代码,数据库和映射配置文件的三者同步更新的操作,工作量大,会加大开发成本,当然好处是当数据库结构或者映射关系发生改变时,只需要修改数据库和配置文件,程序代码不需要更新,从这方面看有利于系统的维护。不过在很多实践中,数据库或映射关系改变时,很容易导致程序代码必须作相应的修改,此时会导致代码,数据库和映射配置文件的同步更新工作。因此映射配置信息采用何种保存模式需要开发者自己权衡,不过在这里由于是要演示使用反射和特性的,因此映射配置信息是保存在代码中的。当然我们可以建立一个ORM框架,既支持使用特性存储映射关系,也可以使用映射配置文件,不过比较复杂,本框架程序是演示程序,不会实现该功能。
开发者在编制存储数据的类型后,使用BindTableAttribute和FieldBindAttribute特性建立了映射关系后,ORM框架程序就能根据这些类型来操作数据库了,目前的设计将提供以下几种功能。
查询数据库,返回对象
使用本功能,框架可以根据指定的SQL查询语句和对象类型查询数据库,并根据查询结果生成若干个对象,并设置刚刚创建的对象的属性值为字段值。在这个功能中,需要首先指定SQL查询语句和对象类型。
在这个功能中,框架程序首先获得对象类型的所有公开属性,获得其附加的BindFieldAttribute特性,获得这些属性绑定的数据库字段名。然后执行SQL查询,遍历所有查询的纪录,对每一个记录都创建一个数据对象,并根据字段和属性的映射关系将数据库字段值保存到对象的属性值中。如此就实现了查询数据库获得对象的功能。
这个功能中需要用户指定SQL查询语言,也可以根据对象类型绑定的数据表名称来自己拼凑SQL语句。
将对象插入到数据库
在本功能中,框架程序使用反射获得对象类型附加的BindTableAttribute特性,获得该对象映射的数据表名;然后遍历所有的公开实例属性,若属性附加了BindFieldAttribute特性,则获得该属性映射的字段名。然后收集所有的属性值和它们映射的字段名,使用字符串拼凑生成一个Insert的SQL语句。然后调用数据库连接对象执行这个SQL语句,以实现向数据库新增记录的功能。
根据对象修改数据库记录
在本功能中,框架程序使用指定的对象来修改数据库中的记录。此时对象类型中至少有一个属性附加了关键字段映射特性。框架程序使用反射获得对象类型附加的BindTableAttribute 特性,获得该对象映射的数据表名,然后遍历属性,获得对象属性和数据库字段之间的映射关系。然后收集属性值,使用字符串拼凑生成一个“Update 数据表名 Set 字段1=属性1的值 ,字段2=属性2的值 ”的SQL语句。然后还遍历属性,找到所有附加了关键字段特性的属性以及绑定的字段名,拼凑出“Where 关键字段1=属性1的值 and 关键字段2=属性2的值”,这样就能拼凑出一个完整的更新数据库用的Update的SQL语句,然后调用数据库连接对象执行这个SQL语句,就能实现更新数据库记录的功能。
根据对象删除数据库记录
在本功能中,框架程序获得对象类型绑定的数据表名,并遍历所有的附加了绑定关键字段的特性,然后拼凑出“Delete From 数据表名Where 关键字段1=属性1的值 and 关键字段2=属性2的值”的SQL语句,然后调用数据库连接对象来执行这个SQL更新语句,这样就实现了删除数据库记录的功能。
框架程序代码说明
根据程序设计,我已经初步的把框架程序开发出来,现在对其源代码进行说明。
数据表绑定信息 BindTableAttribute类型
框架程序中首先定义了BindTableAttribute类型,该类型就保存了对象类型映射的数据库表的名称。其源代码为
/// <summary>
/// 数据表名绑定特性
/// </summary>
[System.AttributeUsage( System.AttributeTargets.Class , AllowMultiple = false ) ]
public class BindTableAttribute : System.Attribute
{
/// <summary>
/// 初始化对象
/// </summary>
public BindTableAttribute( )
{
}
/// <summary>
/// 初始化对象
/// </summary>
/// <param name="name">数据表名</param>
public BindTableAttribute( string name )
{
strName = name ;
}
private string strName = null;
/// <summary>
/// 数据表名
/// </summary>
public string Name
{
get
{
return strName ;
}
}
}
BindTableAttribute演示了如何实现自己的特性。所有的特性都是从System.Attribute类型上面派生的,在定义类型时还使用System.AttributeUsage特性来表明这个自定义特性的使用范围,这里使用了Class样式,表示BindTableAttribute特性只能用在其它的Class类型前面,若放置在Interface或Struct类型前面,或者放在对象成员的前面则会出现编译错误。这里还是用语句 AllowMultiple=false 语句来表明对于一个类型,该特性只能用一次,若一个Class类型前面出现多个BindTableAttriubte,则会出现编译错误。若设置AllowMultiple=true,则该特性可以多次定义,也就是一个Class类型前面可以出现多个相同类型的特性。不过这里我们假设一个对象只能映射到一个数据表上,没有多重映射,因此就指明对同一个类型该特性不能多次使用。
特性也是一个Class类型,可以有多个构造函数,就像C#的new语句一样,我们向类型附加特性时可以使用不同的初始化参数来指明使用特性的那个构造函数。我们附加特性时还可以使用“属性名=属性值”的方法来直接指明特性的属性值。这有点类似VB中调用函数时使用“参数名=参数值”的语法。
该特性中定义了一个Name属性,该属性就是被修饰的对象所映射的数据库表的名称。框架程序就读取BindTableAttribute特性的Name值来作为数据对象映射的数据表名,若Name值为空则认为对象类型的名称就是映射的数据表的名称。
若对象没有附加BindTableAttribute特性,则该对象没有映射到任何数据表上,因此不能让框架程序使用它来操作数据库。
数据字段绑定信息 BindFieldAttribute类型
框架程序中定义了BindFieldAttribute类型,该类型就保存了对象的属性映射的数据库字段的名称,转换格式和关键字段样式,其源代码为
[System.AttributeUsage( System.AttributeTargets.Property , AllowMultiple = false ) ]
public class BindFieldAttribute : System.Attribute
{
/// <summary>
/// 初始化对象
/// </summary>
public BindFieldAttribute( )
{
}
/// <summary>
/// 初始化对象
/// </summary>
/// <param name="name">字段名</param>
public BindFieldAttribute( string name )
{
strName = name ;
}
private string strName = null;
/// <summary>
/// 数据字段名
/// </summary>
public string Name
{
get
{
return strName ;
}
}
private bool bolKey = false;
/// <summary>
/// 该字段为关键字段,可用作查询条件
/// </summary>
public bool Key
{
get
{
return bolKey ;
}
set
{
bolKey = value;
}
}
private string strReadFormat = null;
/// <summary>
/// 数据读取格式化字符串
/// </summary>
public string ReadFormat
{
get
{
return strReadFormat ;
}
set
{
strReadFormat = value ;
}
}
private string strWriteFormat = null;
/// <summary>
/// 数据存储格式化字符串
/// </summary>
public string WriteFormat
{
get
{
return strWriteFormat ;
}
set
{
strWriteFormat = value;
}
}
}//public class BindFieldAttribute : System.Attribute
在BindFieldAttribute中,首先我们使用AttributeUsage特性来描述了这个特性的应用范围,这里使用了System.AttributeTargets.Property来表明该特性只能用于对象类型的属性而不能用于其它任何地方。
这里定义了Name属性,就是其所依附的数据对象的属性映射的数据库字段的名称,若Name值为空则认为属性名就是映射的数据库字段名。若数据对象的属性没有附加BindFieldAttribute特性,则该属下没有映射到任何数据库字段上,框架程序会忽略这个成员属性的存在。
这里还定义了Key属性,用于表明所映射的字段是不是关键字段。框架程序在修改和删除数据库记录时需要获得查询条件,而对象类型中所有的附加了BindFieldAttribute特性且Key值为true的属性就可构造出查询条件,若对象类型中没有任何一个Key值为true的成员属性,则框架程序不能根据其来修改和删除数据库记录。
ReadFormat属性用于指明从数据库读取的原始数据设置到对象属性值时的解析格式。比如数据库中保存了类似“20080603”的格式为“yyyyMMdd”的日期数据,而对象属性的数据类型是DateTime类型。此时我们可以设置ReadFormat值为“yyyyMMdd”则框架程序从数据库获得原始数据后试图用“yyyyMMdd”的格式解析原始数据并获得一个DateTime值,然后设置到对象属性值。
WriteFormat属性类似ReadFormat属性,用于指明将数据对象的属性值按照指定的格式化生成一个字符串并保存到数据库中。
主框架模块 MyORMFramework类型
类型 MyORMFramework是本ORM框架的主要程序模块,它根据类型BindTableAttribute和BindFieldAttribute提供的信息将应用程序对象和数据库的表和字段进行绑定,然后向数据库查询,新增,修改和删除数据库记录。应用程序使用ORM框架也基本上就是创建一个MyORMFramework的实例,然后调用它的成员。
获得对象-数据库绑定信息
框架要实现ORM框架功能,第一步就是得获得应用程序对象和数据库的映射关系,在MyORMFramework类型中定义了GetBindInfo函数来获得这种关系。该函数的参数是应用程序的对象类型,返回值是TableBindInfo类型,该类型就是对象-数据库映射信息。由于BindTableAttribute和BindFieldAttribute类型适合对应用对象类型做标记,但不适合快速查询信息,因此这里额外定义了TableBindInfo和FieldBindInfo类型保存映射关系,这两个类型的代码为
/// <summary>
/// 数据表绑定信息对象
/// </summary>
private class TableBindInfo
{
/// <summary>
/// 数据库表名
/// </summary>
public string TableName = null;
/// <summary>
/// 对象类型
/// </summary>
public Type ObjectType = null;
/// <summary>
/// 绑定信息对象
/// </summary>
public BindTableAttribute Attribute = null;
/// <summary>
/// 绑定的字段信息对象
/// </summary>
public FieldBindInfo[] Fields = null;
/// <summary>
/// 绑定的字段列表,格式为"字段1,字段2,字段3"
/// </summary>
public string FieldNameList = null;
}
/// <summary>
/// 数据字段绑定信息对象
/// </summary>
private class FieldBindInfo
{
/// <summary>
/// 绑定的字段名
/// </summary>
public string FieldName = null;
/// <summary>
/// 绑定的字段序号
/// </summary>
public int FieldIndex = - 1;
/// <summary>
/// 对象属性信息
/// </summary>
public System.Reflection.PropertyInfo Property = null;
/// <summary>
/// 数据类型
/// </summary>
public Type ValueType = null;
/// <summary>
/// 默认值
/// </summary>
public object DefaultValue = null;
/// <summary>
/// 绑定信息对象
/// </summary>
public BindFieldAttribute Attribute = null;
/// <summary>
/// 将对象数据转换为数据库中的数据
/// </summary>
/// <param name="v">对象数据</param>
/// <returns>数据库数据</returns>
public object ToDataBase( object v )
{
if( v == null || DBNull.Value.Equals( v ))
return DBNull.Value ;
string Format = Attribute.WriteFormat ;
if( Format != null && Format.Trim().Length > 0 )
{
if( v is System.IFormattable )
{
v = ( ( System.IFormattable ) v ).ToString( Format , null );
}
}
return v ;
}
/// <summary>
/// 将从数据库中获得的数据转换为对象数据
/// </summary>
/// <param name="v">从数据库获得的原始数据</param>
/// <returns>转化后的对象数据</returns>
public object FromDataBase( object v )
{
// 若数据为空则返回默认值
if( v == null || DBNull.Value.Equals( v ))
return DefaultValue ;
// 进行格式化解析
string Format = Attribute.ReadFormat ;
if( Format != null && Format.Trim().Length > 0 )
{
string Value = Convert.ToString( v );
if( ValueType.Equals( typeof( DateTime )))
{
if( Format == null )
return DateTime.Parse( Value );
else
return DateTime.ParseExact( Value , Format , null );
}
else if( ValueType.Equals( typeof(byte )))
{
return byte.Parse( Value );
}
else if( ValueType.Equals( typeof( short )))
{
return short.Parse( Value );
}
else if( ValueType.Equals( typeof( int )))
{
return int.Parse( Value );
}
else if( ValueType.Equals( typeof( float )))
{
return float.Parse( Value );
}
else if( ValueType.Equals( typeof( double )))
{
return double.Parse( Value );
}
return Convert.ChangeType( Value , ValueType );
}
if( v.GetType().Equals( ValueType ) || v.GetType().IsSubclassOf( ValueType ))
{
// 若数据类型匹配则直接返回数值
return v ;
}
else
{
// 若读取的值和对象数据的类型不匹配则进行数据类型转换
System.ComponentModel.TypeConverter converter =
System.ComponentModel.TypeDescriptor.GetConverter( ValueType );
if( converter != null && converter.CanConvertFrom( v.GetType()) )
{
return converter.ConvertFrom( v ) ;
}
return Convert.ChangeType( v , ValueType );
}
}//public object FromDataBase( object v )
}
类型TableBindInfo用于保存对象类型和数据库表的映射关系,类型FildBindInfo用于保存对象属性和数据库字段的映射关系。TableBindInfo定义了一个Fields字段,是FieldBindInfo类型的数组,用于保存所有的对象属性和数据库字段的映射关系。这样我们就用TableBindInfo和FieldBindInfo组成了一个两层的树状列表,方便我们查询对象和数据库的绑定关系。
函数GetBindInfo的代码为
/// <summary>
/// 在内部缓存的映射信息列表,此处为了提高速度。
/// </summary>
private static System.Collections.Hashtable myBufferedInfos = new System.Collections.Hashtable();
/// <summary>
/// 获得指定类型的数据表映射信息对象
/// </summary>
/// <param name="ObjectType">对象类型</param>
/// <returns>获得的映射信息对象</returns>
/// <remarks>
/// 本函数内部使用了 myBufferedInfos 来缓存信息,提高性能。
/// </remarks>
private TableBindInfo GetBindInfo( Type ObjectType )
{
if( ObjectType == null )
{
throw new ArgumentNullException("OjbectType");
}
// 查找已缓存的映射信息对象
TableBindInfo info = ( TableBindInfo ) myBufferedInfos[ ObjectType ] ;
if( info != null )
{
return info ;
}
// 若未找到则创建新的映射信息对象
BindTableAttribute ta = ( BindTableAttribute ) System.Attribute.GetCustomAttribute(
ObjectType , typeof( BindTableAttribute ));
if( ta == null )
{
return null;
}
TableBindInfo NewInfo = new TableBindInfo();
NewInfo.ObjectType = ObjectType ;
NewInfo.Attribute = ta ;
NewInfo.TableName = ta.Name ;
if( NewInfo.TableName == null || NewInfo.TableName.Trim().Length == 0 )
{
// 若在特性中没有指明绑定的表名则使用默认的对象类型名称
NewInfo.TableName = ObjectType.Name ;
}
System.Text.StringBuilder myFieldList = new System.Text.StringBuilder();
System.Collections.ArrayList fields = new System.Collections.ArrayList();
// 遍历所有的公开的实例属性来获得字段绑定信息
foreach( System.Reflection.PropertyInfo p in ObjectType.GetProperties(
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.Public
))
{
BindFieldAttribute fa = ( BindFieldAttribute ) Attribute.GetCustomAttribute(
p , typeof( BindFieldAttribute ));
if( fa != null )
{
FieldBindInfo NewFieldInfo = new FieldBindInfo();
NewFieldInfo.Attribute = fa ;
NewFieldInfo.FieldName = fa.Name ;
if( NewFieldInfo.FieldName == null || NewFieldInfo.FieldName.Trim().Length == 0 )
{
// 若在特性中没有指明绑定的字段名则使用默认的属性名称
NewFieldInfo.FieldName = p.Name ;
}
if( myFieldList.Length > 0 )
{
myFieldList.Append(",");
}
myFieldList.Append( FixFieldName( NewFieldInfo.FieldName ) ) ;
NewFieldInfo.Property = p ;
NewFieldInfo.ValueType = p.PropertyType ;
NewFieldInfo.DefaultValue = GetDefaultValue( p );
fields.Add( NewFieldInfo );
}
}
NewInfo.Fields = ( FieldBindInfo[]) fields.ToArray( typeof( FieldBindInfo ));
NewInfo.FieldNameList = myFieldList.ToString();
// 缓存绑定信息对象
myBufferedInfos[ ObjectType ] = NewInfo ;
return NewInfo ;
}
为了提高程序性能,本类型内部定义了一个myBufferedInfos的哈希列表,该列表保存了各种应用程序类型和数据库的映射关系,在GetBindInfo函数中,程序首先检查myBufferedInfos列表获得缓存的数据。若缓存中没有所需的数据,则使用Attribute.GetCustomAttribute函数从指定的对象类型中获得附加的BindTableAttribute特性,若没有获得特性则表示对象类型没有附加特性,因此无法获得任何绑定信息。若获得了一个BindTableAttribute对象,则创建一个TableBindInfo对象,填充TableBindInfo中的信息。
接着程序调用对象类型的GetProperties函数获得该对象类型中定义的所有属性,这里使用了一种System.Reflection.BindingFlags类型的参数,该参数就指明获得的属性的样式,其中Instance样式指明只查找实例属性,而不是静态属性;Public样式指明只查找公开的属性(public),不查找私有的属性。GetProperties函数返回的是一个System.Reflection.PropertyInfo类型的数组,然后我们遍历所有找到的属性,对每一个属性调用Attribute.GetCustomAttribute函数获得该属性附件的类型为BindFieldAttribute特性,若找到则表示该对象属性映射到数据库字段中,于是创建一个FieldBindInfo类型的对象,填充该对象。完成对所有的属性的遍历后,我们就设置TableBindInfo的Fields字段,然后将获得的TableBindInfo对象保存在myBufferedInfos中,最后函数返回创建的TableBindInfo对象,这个就是指定应用对象类型的数据库映射信息。
在ORM框架操作数据库时会频繁的调用GetBindInfo函数获得对象类型的数据库映射关系,因此需要使用myBufferedInfos哈希列表缓存这种映射关系,这会比较大的提高框架程序的运行性能。
查询数据
框架定义了一个ReadObjects的函数用于查询数据库获得数据,并根据对象-数据库映射关系创建若干个应用程序对象。其主要代码为
/// <summary>
/// 使用指定的SQL查询语句查询数据库并读取多条数据库记录对象
/// </summary>
/// <param name="strSQL">SQL查询语句</param>
/// <param name="ObjectType">要读取的对象类型</param>
/// <param name="MaxObjectCount">最多读取的对象个数</param>
/// <returns>读取的对象组成的数组</returns>
public object[] ReadObjects( string strSQL , Type ObjectType , int MaxObjectCount )
{
// 检查参数
if( strSQL == null )
{
throw new ArgumentNullException("strSQL");
}
if( ObjectType == null )
{
throw new ArgumentNullException("ObjectType");
}
// 检查数据库映射信息
this.CheckBindInfo( ObjectType , false );
// 检查数据库连接
this.CheckConnetion();
// 创建SQL命令对象
using( System.Data.IDbCommand cmd = myConnection.CreateCommand())
{
// 执行SQL查询,获得一个数据读取器
cmd.CommandText = strSQL ;
System.Data.IDataReader reader = cmd.ExecuteReader(
MaxObjectCount == 1 ?
System.Data.CommandBehavior.SingleRow :
System.Data.CommandBehavior.SingleResult );
System.Collections.ArrayList list = new System.Collections.ArrayList();
TableBindInfo table = this.GetBindInfo( ObjectType );
lock( table )
{
// 设置字段序号,提高性能
foreach( FieldBindInfo field in table.Fields )
{
field.FieldIndex = - 1 ;
}
for( int iCount = 0 ; iCount < reader.FieldCount ; iCount ++ )
{
string name = reader.GetName( iCount );
foreach( FieldBindInfo field in table.Fields )
{
if( EqualsFieldName( name , field.FieldName ))
{
field.FieldIndex = iCount ;
}
}
}
while( reader.Read())
{
// 根据对象类型创建对象实例
object obj = System.Activator.CreateInstance( ObjectType );
// 读取对象属性值
if( InnerReadValues( obj , table , reader ) > 0 )
{
list.Add( obj );
}
if( MaxObjectCount > 0 || list.Count == MaxObjectCount )
{
break;
}
}//while
}//lock
reader.Close();
// 返回读取的对象数组
return list.ToArray();
}//using
}
/// <summary>
/// 从数据读取器中读取数据并填充到一个对象中
/// </summary>
/// <param name="ObjInstance">对象实例</param>
/// <param name="info">数据库绑定信息对象</param>
/// <param name="reader">数据读取器</param>
private int InnerReadValues( object ObjInstance , TableBindInfo info , System.Data.IDataReader reader )
{
// 检查参数
if( ObjInstance == null )
{
throw new ArgumentNullException("ObjectInstance");
}
if( info == null )
{
throw new ArgumentNullException("info");
}
if( reader == null )
{
throw new ArgumentNullException("reader");
}
int FieldCount = 0 ;
// 依次读取各个属性值
foreach( FieldBindInfo field in info.Fields )
{
// 绑定的属性是只读的
if( field.Property.CanWrite == false )
{
continue ;
}
// 没有找到绑定的字段
if( field.FieldIndex < 0 )
{
continue ;
}
// 读取数据字段值
object v = reader.GetValue( field.FieldIndex );
v = field.FromDataBase( v );
// 设置对象属性值
field.Property.SetValue( ObjInstance , v , null );
FieldCount ++ ;
}//foreach
return FieldCount ;
}
该函数的参数为SQL查询语句,要创建的对象类型和最多能创建的对象的个数。在该函数中,首先根据数据库连接对象创建一个命令对象,然后执行SQL查询语句,获得一个System.Data.IDataReader对象,然后调用GetBindInfo函数获得对象类型和数据库的映射信息。首先对数据读取器的各个字段名称和对象类型中绑定的各个属性的名称进行比较,设置各个对象类型的属性对应的字段序号,以加快数据读取速度。
然后遍历数据读取器,对每行记录使用System.Activator.CreateInstance创建指定类型的对象实例,然后调用InnerReadValue函数从数据读取器中读取数据并填充对象的属性值。
本文配套源码