当前位置:
首页 > 编程开发 > Objective-C编程 >
-
c#发现之旅使用反射和特性构造自己的ORM框架(上)
制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
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>
[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框架程序就能根据这些类型来操作数据库了,目前的设计将提供以下几种功能。
查询数据库,返回对象
编缉推荐阅读以下文章
在这个功能中,框架程序首先获得对象类型的所有公开属性,获得其附加的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 ;
}
}
}
编缉推荐阅读以下文章
特性也是一个Class类型,可以有多个构造函数,就像C#的new语句一样,我们向类型附加特性时可以使用不同的初始化参数来指明使用特性的那个构造函数。我们附加特性时还可以使用“属性名=属性值”的方法来直接指明特性的属性值。这有点类似VB中调用函数时使用“参数名=参数值”的语法。
该特性中定义了一个Name属性,该属性就是被修饰的对象所映射的数据库表的名称。框架程序就读取BindTableAttribute特性的Name值来作为数据对象映射的数据表名,若Name值为空则认为对象类型的名称就是映射的数据表的名称。
若对象没有附加BindTableAttribute特性,则该对象没有映射到任何数据表上,因此不能让框架程序使用它来操作数据库。
数据字段绑定信息 BindFieldAttribute类型
框架程序中定义了BindFieldAttribute类型,该类型就保存了对象的属性映射的数据库字段的名称,转换格式和关键字段样式,其源代码为
编缉推荐阅读以下文章
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
编缉推荐阅读以下文章
这里定义了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 )
}
编缉推荐阅读以下文章
反射是.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”后缀去掉,当然也可以写全名。一个编程单元可以附加多个特性。
编缉推荐阅读以下文章
ORM背景
在数据库界,主流的数据库都是关系型数据库,其采用的关系型数据结构模型,无论从数学上还是实践中都相当的成熟,得到非常广泛的应用。在关系型数据结构理论中,所有的数据都组织成一个个相互独立的二维表格,一个数据表有若干行若干列。因此关系型数据库适合存储大量的结构简单的数据,不适合存储复杂的数据结构。
在编程界,面向对象的编程思想及其派生思想占据主流。面向对象的编程思想具有封装,继承,重载等手段来方便的实现比较复杂的数据结构,这适应了现代信息系统包含大量复杂数据结构的特点。因此面向对象的编程思想得到广泛应用。
关系型数据模型和面向对象的编程思想之间存在比较大的差别,数据在两者之间的交换是不大通畅的,就像南京,江北和主城区之间是长江来阻断交通。因此开发人员迫切需要破解这种数据交通的阻断。
以前程序员需要编程,从数据库中读取一个个字段值并赋值到编程对象的一个个字段或属性上,这就像在长江上架一个独木桥,开发效率低下,维护困难。后来出现一种叫ORM的框架性的程序,它能根据某些配置信息将数据库中的字段和编程对象的字段或属性之间建立映射关系,从而能方便的从数据库读取字段值并赋值到对象属性中。这是一种半自动的机制,能比较大的提高开发效率,简化维护,这就像在长江上架设了一座高速公路大桥。
从本质上说,关系型数据库和面向对象的编程思想之间的隔阂非常大,就像长江是天涧,即使建五六座大桥也不够用。彻底的解决方法就是抛弃关系型数据库而使用面向对象的数据库,不过这个过程就像南京江北整体搬迁到江南一样,工程浩大,时间漫长。在等待面向对象数据库的统治前,我们仍然得忍受关系型数据库和面向对象编程思想之间的数据交通不畅的痛苦,并使用ORM框架来很有限的降低这种痛苦。从这个角度上说,我们痛恨关系型数据库,就像搞运载火箭的人痛恨地球引力一样。
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
反射是.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”后缀去掉,当然也可以写全名。一个编程单元可以附加多个特性。
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
/// 应用程序的主入口点。
/// </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中的某个字段上,当然不是所有的对象类型的属性映射到数据库字段。通过在程序代码中,我们可以使用硬编码的方式将对象类型及其属性映射到数据库中的表和字段上面。
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
图片看不清楚?请点击这里查看原图(大图)。
这种将映射信息保存在代码中的方式有利有弊,好处是程序代码比较集中,修改代码方便,坏处就是当数据库结构或者映射关系发生改变时,需要修改代码,这导致重新编译重新部署。一些ORM框架使用XML配置文件来保存对象和数据库的映射关系,不过这会导致代码,数据库和映射配置文件的三者同步更新的操作,工作量大,会加大开发成本,当然好处是当数据库结构或者映射关系发生改变时,只需要修改数据库和配置文件,程序代码不需要更新,从这方面看有利于系统的维护。不过在很多实践中,数据库或映射关系改变时,很容易导致程序代码必须作相应的修改,此时会导致代码,数据库和映射配置文件的同步更新工作。因此映射配置信息采用何种保存模式需要开发者自己权衡,不过在这里由于是要演示使用反射和特性的,因此映射配置信息是保存在代码中的。当然我们可以建立一个ORM框架,既支持使用特性存储映射关系,也可以使用映射配置文件,不过比较复杂,本框架程序是演示程序,不会实现该功能。
开发者在编制存储数据的类型后,使用BindTableAttribute和FieldBindAttribute特性建立了映射关系后,ORM框架程序就能根据这些类型来操作数据库了,目前的设计将提供以下几种功能。
查询数据库,返回对象
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
在这个功能中,框架程序首先获得对象类型的所有公开属性,获得其附加的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语句,就能实现更新数据库记录的功能。
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
在本功能中,框架程序获得对象类型绑定的数据表名,并遍历所有的附加了绑定关键字段的特性,然后拼凑出“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 ;
}
}
}
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
特性也是一个Class类型,可以有多个构造函数,就像C#的new语句一样,我们向类型附加特性时可以使用不同的初始化参数来指明使用特性的那个构造函数。我们附加特性时还可以使用“属性名=属性值”的方法来直接指明特性的属性值。这有点类似VB中调用函数时使用“参数名=参数值”的语法。
该特性中定义了一个Name属性,该属性就是被修饰的对象所映射的数据库表的名称。框架程序就读取BindTableAttribute特性的Name值来作为数据对象映射的数据表名,若Name值为空则认为对象类型的名称就是映射的数据表的名称。
若对象没有附加BindTableAttribute特性,则该对象没有映射到任何数据表上,因此不能让框架程序使用它来操作数据库。
数据字段绑定信息 BindFieldAttribute类型
框架程序中定义了BindFieldAttribute类型,该类型就保存了对象的属性映射的数据库字段的名称,转换格式和关键字段样式,其源代码为
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
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
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
这里定义了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的实例,然后调用它的成员。
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
框架要实现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 )
}
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
反射是.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”后缀去掉,当然也可以写全名。一个编程单元可以附加多个特性。
编缉推荐阅读以下文章
- C#发现之旅: WinForm.NET中开发具有固定背景图片的可滚动控件
- C#发现之旅:于动态编译的VB.NET脚本引擎(下)
- C#发现之旅:于动态编译的VB.NET脚本引擎(上)
- C#发现之旅:基于反射和动态编译的快速ORM框架(下)
- C#发现之旅:基于反射和动态编译的快速ORM框架(上)
- C#发现之旅:使用反射和特性构造自己的ORM框架(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(下)
- C#发现之旅 - 高性能ASP.NET树状列表控件(中)
- C#发现之旅 - 高性能ASP.NET树状列表控件(上)
- C#发现之旅:C#开发Windows Service程序(下)
最新更新
nodejs爬虫
Python正则表达式完全指南
爬取豆瓣Top250图书数据
shp 地图文件批量添加字段
爬虫小试牛刀(爬取学校通知公告)
【python基础】函数-初识函数
【python基础】函数-返回值
HTTP请求:requests模块基础使用必知必会
Python初学者友好丨详解参数传递类型
如何有效管理爬虫流量?
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
SQL Server -- 解决存储过程传入参数作为s
关于JS定时器的整理
JS中使用Promise.all控制所有的异步请求都完
js中字符串的方法
import-local执行流程与node模块路径解析流程
检测数据类型的四种方法
js中数组的方法,32种方法
前端操作方法
数据类型
window.localStorage.setItem 和 localStorage.setIte
如何完美解决前端数字计算精度丢失与数