Grove在O/R Mapping中的应用

时间:2022-10-25 06:22:18

Grove在O/R Mapping中的应用

[摘要] 本文论述了O/R Mapping的运行机制和在.NET应用程序开发中应用Grove工具如何实现O/R Mapping映射框架。

[关键词] Grove O/R Mapping SQL

一、引言

面向对象思想在当今应用程序开发中占有主导地位,而大部分的企业级应用都是建立在数据库基础上,因此数据库的对象化为构建企业级应用提供极为便利的条件。目前大部分面向对象的系统采用的做法一般为:

在两层架构中的客户端硬编码SQL语句,将获取的数据还原成类的实例;将数据库的访问独立出来,连同业务逻辑作为中间层。仍然通过编码的方式进行对象与关系数据库之间的O/R转换。

这样在开发过程中,常常有一些业务逻辑需要直接写SQL语句实现,其结果是:遍地布满SQL语句。这些高藕合的SQL语句给系统的改造和升级带来很多无法预计的障碍。当数据库结构或是类抽象中的任何一方发生变化,这样的结构不可避免地导致系统的大幅修改。同时,编码中的很大一部分将用于实现关系数据库与类实例的转换。

所以,为了提高项目的灵活性,特别是快速开发,我们选择在数据访问层与数据存储层中间加入ORM层。这样当数据库模型改变时,不再需要理会逻辑代码和SQL语句中涉及到该模型的所有改动,只需要将该模型映射的对象稍作改动,甚至不做改动就可以满足要求。

为此我们在设计应用程序框架中提出一个模型:在业务逻辑层和关系数据库之间增加一个Persistence Layer,负责实现对象和关系数据库之间的映射。利用这个O/R Mapping框架的机制,对象与关系数据库之间的转换就可以透明地进行,而不用去关心数据库连接、并发性、事务等等问题。也就是说,业务逻辑层直接获取或存贮的就是清晰的对象,中间的转换过程就交给Mapping框架处理了(如图1所示)。

二、O/R Mapping机制

根据抽象对象与关系数据库的特性,O/R Mapping在以下几个方面实现:

1.类属性映射到数据表列

类属性和表列并非一一对应。一个类属性可对应0或多个实体表的字段。例如:作为保存中间结果,或是标明类在某一运行时刻状态就不需要保存在数据库中,作为对象在数据库中的惟一标识,同时为了避免因业务规则变化而引起的数据库字段和键值的频繁变化,通常采用无业务意义的字段OID作为各个实体表的主键。OID也作为类与数据库映射时的对象的惟一标识。

2.类映射到数据表

不同层次的类映射到数据表时,应该根据实际的系统设计结果,从以下几个角度权衡,采用适当的映射策略:实现难易度、数据访问难易度、对象与数据库耦合度、数据访问速度、数据库字段冗余度以及多态性支持程度。

可以采用的映射策略主要分为以下三种:

(1)一个类层次对应一个数据表

这里所说的一个类层次,指的是父类及其所有子类。将父类和子类中有持久性需求的属性设置为同一数据表的字段。此方法实现起来简单,并且较好地支持多态。因为不同的子类可以用一个标志位加以区分。子类实例的转换比较容易实现。但是,类与数据库之间的耦合程度高。由于子类所特有的属性被所有类层次中的对象所共有,数据库中冗余字段较多。

(2)一个实体类对应一个数据表

各个子类所特有的属性,联合从父类中继承的公共属性,构成表的结构。父类不映射为数据库中的实体表,它只作为子类公共属性的载体。简单图如图2所示。

这种映射模型使得类属性值的保存和对象还原实现方便。缺点同样是类结构与数据库的耦合程度高,特别父类属性变列时,所有从此继承下来的子类都需要进行变更。

此外,对多态性的支持较差。子类的角色转换需要在子类对应的数据表之间准确地传递适当的属性值,同时,需要赋予新的OID。例如,由ConcreteClass1中的一个实例转换成ConcreteClass2的一个实例,即通常情况下的角色转换。由于父类没有对应的实体,不得不将该实例在ConcreteClassTable1中的公共属性值抽取出来,通过Parent类构建ConcreteClass2的实例,给 SpecAttributeE和SpecAttributeF赋值。在持久性保存时,在 ConcreteClassTable2中生成一个新的OID。这种情况下,就不如第一种映射模型,用同一个OID,在同一张表内就可以实现不同子类之间的转换。

(3)一个类对应一个数据表

无论是父类还是子类,只要类中的属性有持久性保存的需要,就将类映射到数据表。子类的表以父类类表的OID为外键。在关系数据库中最大程度地实现了类的多态性。与前两者比较,对象与数据库的耦合程度是最低的,某一个类属性的变更引起的表结构变动最少。

这种方法的缺点在于生成子类实例时,继承层次多的子类的属性值还原困难。由于子类的公共属性包含在父类对应的数据表中,当需要单独获取子类实例时,需要从多个数据表中获取数据合成完整的实例。当类的层次较多时,子类的访问可能会成为系统与数据库交互的瓶颈。

3.类间关系映射为键值

数据库的表现形式,决定了只能体现类关系中的关联和聚合,对其他的关系,只能在业务逻辑中以编码的方式实现。

一对一、一对多的关联是通过在关联的某一方(一般在关联角色多重性≥1的一方)引用对方的OID,并视关系的紧密程度为外键的字段加上非空以及唯一性的约束。在生成相关联的类实例时,可根据外键自动获取另一方的实例。多对多的关联需要创建关联表。关联表是统一的OID作为主键,同时以关联角色双方的OID作为联合外键。

聚合关系映射到数据库中与通常所说的主附表结构类似,在聚合关系中的子类对应的数据表中含有指明父类的OID的域。特别是强制型聚合(或称之为组合),子类单独存在是没有意义的,必须与父类同时存在、同时消亡。当然,数据库键值本身只是这一关系的体现。Grove实现了关联类自动生成的功能,大大提高了O/R转换的效率。

三、Grove在O/R Mapping中的应用

1.O/R Mapping 的工具实现-Grove

Grove是一个基于.NET的O/R Mapping框架,优秀的ORM框架不仅可以帮助我们很好的理解对象及对象的关系,而且工具本身会帮助我们维护这些关系。基于这个理念,我们设计应用系统时用了基于.NET的ORM工具――Grove ORM Development Toolkit。

Grove ORM Development Toolkit包含Grove和Toolkit两部分内容。Grove为ORM提供对象持久、关系对象查询、简单事务处理、简单异常管理等功能。数据持久包括一些对象的Insert、Delete、Update、Retrieve等功能,关系对象查询则提供一些基于对象的复杂关系查询,包括对应到数据库功能的子查询、关联查询(JOIN)、函数支持(count、avg、max、min)、聚合等。Toolkit是基于 2002/2003的VSIP开发的外接程序,职责是帮助开发人员快速映射关系数据库中的业务模型到符合Grove要求的映射实体类,以及映射数据库中复杂关系查询到Grove要求的关系映射实体,暂时只提供C#支持。图3是Grove内部类实现关系图。

在ORM实现的前期工作中,为了实现屏蔽各种数据库之间的操作差异,我们需要定义数据操作公有接口,封装基本的数据库Insert,Update,Delete,Query等操作。

public interface IDbOperator

{

int ExecNonQuery(string cmdText);

int ExecDataSet(string cmdText,DataSet entity);

object ExecScalar(string cmdText);

}

再定义一个数据库操作工厂类,实现各种不同类型数据的适配。

public abstract class DbOperatorFactory:IDbOperator

然后实现各种数据库的操作类,以SQLServer为例。

internal class SqlDbOperator:DbOperatorFactory

完成后,就是ORM主角――实体(Entity)的实现。ORM中实体的定义可以通过实体类自身包含数据模型元数据的方式实现,也可以通过定义XML的元描述实现。我们在应用系统中是通过实体类自身映射的实现。

2.实体类映射

实体(Entity)是实际业务数据的载体,包含业务数据模型的元描述,可以直接由数据库中的某张表或视图生成,也可以根据需要手工创建。.NET中提供了System.Attribute,该类包含访问和测试自定义属性的简便方法。.NET Framework预定义了一些属性类型并使用它们控制运行时行为。我们可以通过继承System.Attribute基类自定义用于描述实体对象映射时所需要的数据模型元数据,包括表名,字段名,字段长度,字段类型等一些常用的数据。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]

public class DataTableAttribute : Attribute

AttributeUsage用来表示该自定义属性可以被绑定在什么样的对象上,这里表示应用在Class或者Struct之上。

抽象一些具有相同特征的属性,使之成为自定义属性的基类。

[AttributeUsage(AttributeTargets.Property)]

public class BaseFieldAttribute:Attribute

定义一般字段所需要的自定义属性类。

[AttributeUsage(AttributeTargets.Property)]

public class DataFieldAttribute : BaseFieldAttribute

定义关键字字段所需的自定义属性类。

[AttributeUsage(AttributeTargets.Property)]

public class KeyFieldAttribute : BaseFieldAttribute

定义外键字段所需要的自定义属性类。

[AttributeUsage(AttributeTargets.Property)]

public class ForeignKeyFieldAttribute : BaseFieldAttribute

在以上自定义属性类完成后,我们需要一个用于访问实体在运行期绑定的自定义属性及属性数据的一个Help类。

internal class TypeHelper

实体定义完成后,我们需要根据实体类中绑定的自定义属性构造出运行期需要的SQL语句,为了收集实体类定义中的数据结构描述,我们需要定义一个类来说明实体在运行期所引用到的所有关于数据持久的信息,包括关键字字段,外键字段,一般字段等。

internal class TypeInfo

同时需要一个字段元数据描述类,描述字段在数据库中的名称、大小、是否可为空、列类型等信息。

internal class ColumnInfo

以上条件具备后,我们需要定义一个解析类,负责转换数据的程序类型到数据库字段类型,并且构造出Insert,Update,Delete,Query等操作所需要的SQL语句。

internal class SqlEntityParser

将上面的操作组合起来就是实体类对象操作员。

public class ObjectOperator

实现新增一个记录到数据库中,就是创建了一个新的实体对象,并交由对象操作员进行持久化。

public void Insert(object o)

{

TypeInfo info1=new TypeInfo(o.GetType());//根据实例或者实体描述信息

SQLCommand sc=info1.BuildInsertCommand(o);//构造SQL命令对象

DbOperator.Parameters=sc.Parameters;//赋值SQL命令所需的参数

DbOperator.ExecNonQuery(mandText);//执行SQL命令

}

这里的SQLCommand对象封装了SQL命令处理时所需要的一些值,包含SQL语句,命令参数(Parameter)等。

四、结语

Grove在.NET下实现了ORM对象关系映射框架,应用该对象关系映射(Object Relational Mapping, ORM)框架对关系型数据库对象化,实现了持久层数据的对象化处理。

参考文献:

[1]卫伟陆慧娟:微软.Net开发平台初探.微机发展,2003(6).89-90

[2]任开银黄东:在.Net上架构企业级应用程序.微型机与应用,2003(1).29-30

注:本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。

上一篇:经济学方法论的协调走向 下一篇:金融人才培养与课程教学创新