Internet Explorer 6.0IEVisual Studio .NET 2002SQL Server 2000DBAMFCHTMLIntermediateDevVisual StudioSQL ServerSQLWindowsC++.NETVisual BasicC#
O/R 映射工具的十大必备功能






2.55/5 (8投票s)
2005 年 9 月 12 日
16分钟阅读

42446
O/R 映射工具日益普及,人们逐渐认识到它们为开发者带来的生产力提升。然而,许多人对 O/R 映射的了解不足,无法考虑使用这些工具;另有许多人对使用任何代码生成器(包括 O/R 映射工具)心存疑虑。
O/R 映射工具的十大必备功能
作者:伊克巴尔·汗
O/R 映射工具日益普及,人们逐渐认识到它们为开发者带来的生产力提升。然而,许多人对 O/R 映射的了解不足,无法考虑使用这些工具;另有许多人对使用任何代码生成器(包括 O/R 映射工具)心存疑虑。
在本文中,我将尝试向您介绍一个优秀的 O/R 映射工具所提供的各种重要功能,以及它如何对您有所助益。我不会讨论任何特定的 O/R 映射工具,而是泛指所有工具。
什么是 O/R 映射?
如果您正在开发一个需要关系型数据库的面向对象应用程序,您将需要开发与数据库设计建模的持久化对象,并了解如何与数据库中的不同表进行交互。您可以手动开发这些对象,也可以使用 O/R 映射工具快速映射和生成它们。希望在阅读本文后,您会相信在大多数情况下,手动开发是一个糟糕的主意。
O/R 映射工具连接到您的数据库并读取其模式,然后让您将持久化对象映射到数据库表和视图,将单行事务操作、查询和存储过程调用指定为这些对象的方法。此外,它还允许您根据数据库中的关系定义对象之间的一对一、一对多、多对一和多对多关系。然后,它为您生成完全可用的持久化对象代码。下面是一些持久化对象的简单示例。请注意,如《.NET 领域对象持久化模式》中所述,这种持久化设计模式将持久化对象分为“领域对象”和“工厂对象”。如果您愿意,可以阅读更多关于此设计模式的信息。
/* 注意通过“reports_to”实现的一对多自引用关系 */
CREATE TABLE t_employees (
employee_id int IDENTITY (1, 1) NOT NULL,
name nvarchar (40) NOT NULL,
birth_date datetime NULL,
photo image NULL,
reports_to int NULL,
)
// t_employees 表的领域对象类
public class Employee
{
Int32 _employeeId;
String _name;
DateTime _birthDate;
Byte[] _photo;
Int32 _reportsTo;
ArrayList _subordinates;
Employee _supervisor;
Int32 EmployeeId {
get { return _employeeId;} set { _employeeId = value; }
}
String Name {
get { return _name; } set { _name = value; }
}
DateTime BirthDate {
get { return _birthDate; } set { _birthDate = value; }
}
Byte[] Photo {
get { return _photo; } set { _photo = value; }
}
Int32 ReportsTo {
get { return _reportsTo; } set { _reportsTo = value; }
}
ArrayList Subordinates {
get { return _subordinates; } set { _subordinates = value; }
}
Employee Supervisor {
get { return _employee; } set { _employee = value; }
}
}
// Employee 类的持久化对象
public interface IEmployeeFactory
{
void Load(Employee object, int nDepth);
void Insert(Employee object);
void Update(Employee object);
void Delete(Employee object);
// 查询方法
ArrayList FindSomeEmployees();
// 关系方法
void LoadSupervisor ( Employee emp);
void LoadSubordinates(Employee emp, int nDepth);
}
以下是客户端应用程序如何使用此代码的示例
public class NorthwindApp
{
static void Main (string[] args) {
Employee emp = new Employee();
EmployeeFactory empFactory = new EmployeeFactory();
// 让我们从 Northwind 数据库加载一名员工。
emp.EmployeeId = 10045;
empFactory.load(emp);
// empList 是 Employee 对象的集合
ArrayList empList = empFactory.FindSomeEmployees();
// subList 是 Employee 的下属集合
对象
ArrayList subList = empFactory.LoadSubordinates(emp, 1);
// supervisor 是 Employee 的主管对象
Employee supervisor = empFactory.LoadSupervisor(emp);
}
}
功能 1:灵活的对象映射
O/R 映射的一切都始于将您的对象映射到您的关系表。以下是您应该了解的此领域的一些特定功能
1. 表和视图映射:该工具应允许您将对象映射到关系数据库中的表和视图。映射到视图很重要,因为许多实际应用程序倾向于使用视图而不是表。
2. 多表映射:该工具应允许您不仅将对象映射到单个表,还可以映射到多个表,并指定这些表之间的连接。如果您的应用程序需要获取跨多个行的列表(在 Web 应用程序中很常见),您将需要此功能。
3. 命名约定:该工具应允许您在对象及其属性中使用与关系数据库中不同的命名约定。如果您的数据库表名为 t_employees,您的对象可能需要命名为 Employee。
4. 属性映射:该工具应支持多项功能
a. 主键:您的对象必须将主键与其他列区分开来。它还应允许您使用单列或多列主键。
b. 自动生成列:某些列是自动生成的(IDENTITY 或 SEQUENCE),您的对象必须包含代码来处理插入后获取生成的值。
c. 只读列:某些列不应由客户端设置,而是由系统生成其值(例如,使用 SQL Server 中的 getDate() 函数的 creation_dtime 列)。您的对象必须包含适当的代码来获取这些系统生成的值。
d. 必填列:您的对象必须在插入或更新操作时对必填列进行数据验证。这比仅仅为了获取错误消息而浪费一次数据库往返要高效得多。
e. 验证:在大多数情况下,您已在数据库列上定义了各种约束。如果能在持久化对象中进行相同的验证,就可以避免不必要的数据库往返,只为接收一条错误消息。
f. 公式字段:在许多情况下,当您从数据库获取数据时,您使用的是正则表达式而不是列(例如,AnnualSalary 对象属性可能是一个公式字段 monthly_salary * 12)。
g. 数据类型映射:有时,您希望将数据库中的一种数据类型映射到对象中的另一种数据类型。例如,datetime 类型可能会转换为字符串。您的对象必须具有自动执行此操作的双向逻辑(读取和写入)。
功能 2:使用您现有的领域对象
正如您所见,一种流行的设计模式将持久化对象分为“领域”和“工厂”对象。O/R 映射的一个重要功能是让您决定是生成领域对象和工厂对象,还是使用您现有的领域对象并仅生成了解您的领域对象的工厂对象。
有些人不想生成“领域”对象,而是手动开发它们,只生成“工厂”对象。其原因是他们的领域对象几乎在应用程序的所有子系统中都使用,因此他们不希望它们在随后的代码重新生成中频繁更改。但是,他们不介意生成“工厂”对象,因为它们的使用仅限于少数几个地方(用于加载和保存操作)。
因此,O/R 映射工具应允许您使用现有的领域对象,并仅映射和生成工厂对象。它应使用 .NET 反射读取您的领域对象定义,并在您完成映射后,以这些工厂对象使用您的领域对象来保存所有数据的方式生成工厂对象。
功能 3:事务操作(CRUD)
数据库事务允许您将多个操作分组为一个原子操作,因此所有操作要么全部成功,要么全部失败。事务操作包括创建、读取、更新和删除(也称为插入、更新、加载和删除)。每个事务操作仅对表中的一行数据执行。
您将在两种主要的事务环境中工作,您的 O/R 映射工具需要了解这两种环境,以便相应地生成代码。这些选项是:
1. COM+/MTS:Microsoft Transaction Server (MTS) 管理应用程序的所有事务。您的对象不启动、提交或回滚事务。它们只从其方法返回成功或失败,MTS 决定何时执行“BeginTrans”、“Commit”或“Rollback”。此外,您的所有工厂对象都是无状态的,因此 MTS 可以对其进行对象池化。这是一种特定的设计模式,您的 O/R 映射工具必须理解并生成您的持久化对象以符合它。此环境最常见的应用程序是 ASP.NET 应用程序和 .NET Web Services。
2. 独立:这是您的应用程序自行管理所有事务的环境。它需要知道何时执行“BeginTrans”、“Commit”和“Rollback”。您的 O/R 映射工具需要了解此环境并生成符合它的代码。这种情况最常见的是基于 Windows 窗体的客户端/服务器应用程序,它们直接与数据库服务器通信。
功能 4:关系和生命周期管理
关系型数据库的基础是表之间存在关系。同样,当您将对象映射到这些表时,您的对象也需要与其他映射对象建立相同的关系。因此,您的 O/R 映射工具必须通过让您确定要在对象中保留哪些关系来支持这一非常重要的功能。以下是您必须具备的不同类型的关系:
1. 一对一关系:在此关系中,您的对象必须包含对正好一个其他对象的引用,并且必须处理其加载和保存场景。
2. 多对一关系:这与一对一关系非常相似,您的对象必须包含对正好一个其他对象的引用,并且必须处理其加载和保存场景。
3. 一对多关系:在此关系中,您的对象必须包含相关对象的集合,并且必须处理使用加载操作加载它们,以及使用保存操作添加和删除它们。
4. 多对多关系:这是最复杂的关系,涉及数据库中的桥接表来建立关系。桥接表有两种不同的情况,如下所述:
a. 仅包含主键的桥接表:在这种情况下,桥接表只包含主键(实际上由多个外键组成)。因此,您的对象无需拥有任何桥接表属性,只需保留相关对象的集合(类似于一对多)。事实上,您对象的公共接口通常与一对多相同,但由于桥接表,底层代码有所不同。
b. 包含附加列的桥接表:这是最复杂的情况,因为桥接表具有您的对象必须处理的附加有用列。您的对象需要加载包含桥接表和相关表信息的复合对象集合。
生命周期管理功能必须包括加载主(或父)对象的能力,并通过该对象加载所有相关对象(即所有不同关系)。您还应该能够从关系中添加新创建的相关对象或删除现有相关对象。当您保存主对象时,它必须与所有关系信息一起保存(作为一个事务)。
一个好的 O/R 映射工具将允许您定义所有类型的关系,并处理生成代码中的生命周期管理。
功能 5:对象继承
正如您已经知道的,面向对象编程的一个非常重要的方面是继承。然而,关系数据库不会在关系模型中自动提供继承。但是,有许多模式可以将对象继承映射到关系数据库。一个好的 O/R 映射工具必须提供此功能。
以下是对象继承映射到关系数据库的几种方式。
1. 每个对象一张表:这是最流行和灵活的模式。在此模式中,每个对象都映射到数据库中自己的表。每个基对象和其派生对象之间存在一对一关系。此关系的外键保存在派生对象中。它是最灵活的,因为无需更改任何现有表的结构,我们就可以不断向继承层次结构添加内容。但是,对于加载基对象和派生对象而言,它的效率不高,因为每个对象都进行了单独的“加载”操作。
2. 所有对象一张表:在此模式中,基对象和所有派生对象都表示在数据库中的一张表中。此表包含表示所有对象属性的列。它对于加载和保存数据效率最高,但限制很大,因为向继承中添加新对象需要更改数据库中现有表的结构,这是非常不理想的。
考虑到这一点,O/R 映射工具至少必须支持“每个对象一张表”的方法,如果它也能支持第二种方法,那就锦上添花了。基类和派生类的生成代码应处理以下情况:
1. 插入和更新操作:派生类必须首先要求基类执行插入或更新,然后执行自己的操作。但基类和派生类的操作都必须在一个事务中执行。
2. 删除操作:与插入和更新操作不同,删除操作首先在派生对象上执行,然后是基对象。然而,两者都必须在一个事务中完成。
3. 加载操作:派生类中的加载操作也必须调用基类上的加载,并且这两者都应该在一个事务中完成。
功能 6:静态查询和动态查询
数据库应用程序做的下一件最常见的事情是从一个或多个表中检索数据行。应用程序通过使用 SQL 查询(SELECT 语句)来完成此操作。然而,面向对象应用程序希望获取对象的集合,而不是行。因此,O/R 映射工具必须为您提供一种创建返回对象集合的查询的方法。
静态查询是在编译时定义的,运行时唯一改变的是参数值。这些查询可以预编译并高效运行。因此,O/R 映射工具必须允许您将静态查询定义为对象的方法,并指定这些查询是否接受任何运行时参数。
另一方面,动态查询是那些在运行时创建查询或其条件的查询。这些查询无法预编译,必须作为“动态 SQL”运行。然而,这些查询的好处是它们允许您的应用程序中出现即席搜索操作的情况,并根据用户输入确定查询应该是什么样子。这些查询也需要作为方法提供给您的对象,但具有灵活性,您可以在运行时指定“WHERE 子句”和“ORDER BY 子句”。
功能 7:存储过程调用
存储过程在高事务环境中变得非常流行,因为它们允许您将所有 SQL 放入 DBMS 中并以编译形式存在。因此,您的 SQL 不必在运行时编译,因为这是一个非常昂贵的过程。O/R 映射工具必须支持以下两种存储过程情况:
1. 现有存储过程:第一种情况是您已经在 DBMS 中拥有自定义存储过程,并且希望您的持久化对象调用它们。在这种情况下,O/R 映射工具必须允许您在对象中定义可以调用存储过程的方法。它还必须支持不同的参数类型(in、out、in/out),以及存储过程是否返回记录集。如果存储过程返回记录集,则对象必须将此数据返回给其客户端。
2. 生成存储过程:第二种情况是,所有 SQL(动态查询除外)将作为对象关系映射的结果生成,并作为存储过程放入 DBMS 中,并且您的对象代码会生成以调用这些存储过程。如果您没有为所有 SQL 生成存储过程,它将作为“动态 SQL”放入您的对象源代码中。
功能 8:对象缓存
如果您的应用程序事务密集并支持高流量,那么您真的离不开内置在应用程序中的有效缓存。Microsoft 提供了 ASP.NET Cache 对象,但它不足以应对集群环境,因为您的应用程序在多台服务器上运行,需要一个也集群的缓存。然而,.NET 中有可用于集群环境的商业缓存解决方案。
无论您使用哪种缓存产品,都必须确保您的持久化对象从适当的位置发出缓存调用。并且,您的 O/R 映射工具应提供生成代码的能力,该代码可以向一个或多个领先产品发出缓存调用。
理想情况下,您的 O/R 映射工具应该允许您指定要缓存哪些对象以及不缓存哪些对象。最常见的情况是事务对象(单行对象)被缓存。但是,您也可以缓存整个集合甚至相关对象。
功能 9:生成代码的定制
您总会遇到需要定制生成代码的情况。但是,如果您更改了生成代码,下次再次生成代码时很可能会被覆盖。而且,由于软件开发是一个迭代过程,您将不得不多次生成代码。
此外,您编写的任何自定义代码都必须在生成代码运行时被调用。并且,它还必须能够控制生成代码的后续执行。例如,如果您的自定义代码在执行“插入”之前被调用,并且您发现有问题,您应该能够阻止“插入”实际发生。
为了防止您的代码被覆盖,O/R 映射工具必须允许您将代码标记为“安全代码”,这样在将来的代码重新生成中就不会被覆盖。为了确保您的自定义代码无缝调用,O/R 映射工具需要支持“挂钩”的概念,这些挂钩是从生成代码中的战略位置进行的调用,这些“挂钩”返回的结果代码决定了接下来会发生什么。或者,O/R 映射工具需要允许您派生生成代码,然后使用多态性实际运行您的代码而不是生成代码。然后您可以决定是否调用“基类代码”。
功能 10:模板定制
一个好的 O/R 映射工具很可能使用代码模板来确定如何生成代码。O/R 映射工具将其拥有的模板与您的对象映射输入和数据库模式信息的组合相结合,以精确确定如何生成代码。
由于 O/R 映射工具是从模板生成代码,如果它允许您修改这些模板(或添加新模板),以便您可以影响生成代码的外观,那将是非常棒的。
一个非常简单的例子是,当您希望在每个源代码文件中加入自己的版权头。如果您可以在代码模板文件中插入此头,那么下次生成代码时它将自动使用。您还应该能够编写自己的代码模板(尽管这仅适用于高级用户),并让 O/R 映射工具使用您的模板,但以它一贯的方式完成所有其他操作。
结论
您应该认真考虑使用 O/R 映射工具,因为它将为您节省大量的开发和测试时间。在评估哪种工具最适合您时,您应该知道要寻找什么。我希望本文能帮助您更好地理解 O/R 映射。
伊克巴尔·汗是 AlachiSoft (http://www.alachisoft.com/) 的联合创始人,AlachiSoft 是一家领先的 .NET O/R 映射工具 TierDeveloper 和 .NET 集群对象缓存 NCache 的提供商。您可以通过 iqbal@alachisoft.com 与他联系。
作者:伊克巴尔·汗
O/R 映射工具日益普及,人们逐渐认识到它们为开发者带来的生产力提升。然而,许多人对 O/R 映射的了解不足,无法考虑使用这些工具;另有许多人对使用任何代码生成器(包括 O/R 映射工具)心存疑虑。
在本文中,我将尝试向您介绍一个优秀的 O/R 映射工具所提供的各种重要功能,以及它如何对您有所助益。我不会讨论任何特定的 O/R 映射工具,而是泛指所有工具。
什么是 O/R 映射?
如果您正在开发一个需要关系型数据库的面向对象应用程序,您将需要开发与数据库设计建模的持久化对象,并了解如何与数据库中的不同表进行交互。您可以手动开发这些对象,也可以使用 O/R 映射工具快速映射和生成它们。希望在阅读本文后,您会相信在大多数情况下,手动开发是一个糟糕的主意。
O/R 映射工具连接到您的数据库并读取其模式,然后让您将持久化对象映射到数据库表和视图,将单行事务操作、查询和存储过程调用指定为这些对象的方法。此外,它还允许您根据数据库中的关系定义对象之间的一对一、一对多、多对一和多对多关系。然后,它为您生成完全可用的持久化对象代码。下面是一些持久化对象的简单示例。请注意,如《.NET 领域对象持久化模式》中所述,这种持久化设计模式将持久化对象分为“领域对象”和“工厂对象”。如果您愿意,可以阅读更多关于此设计模式的信息。
/* 注意通过“reports_to”实现的一对多自引用关系 */
CREATE TABLE t_employees (
employee_id int IDENTITY (1, 1) NOT NULL,
name nvarchar (40) NOT NULL,
birth_date datetime NULL,
photo image NULL,
reports_to int NULL,
)
// t_employees 表的领域对象类
public class Employee
{
Int32 _employeeId;
String _name;
DateTime _birthDate;
Byte[] _photo;
Int32 _reportsTo;
ArrayList _subordinates;
Employee _supervisor;
Int32 EmployeeId {
get { return _employeeId;} set { _employeeId = value; }
}
String Name {
get { return _name; } set { _name = value; }
}
DateTime BirthDate {
get { return _birthDate; } set { _birthDate = value; }
}
Byte[] Photo {
get { return _photo; } set { _photo = value; }
}
Int32 ReportsTo {
get { return _reportsTo; } set { _reportsTo = value; }
}
ArrayList Subordinates {
get { return _subordinates; } set { _subordinates = value; }
}
Employee Supervisor {
get { return _employee; } set { _employee = value; }
}
}
// Employee 类的持久化对象
public interface IEmployeeFactory
{
void Load(Employee object, int nDepth);
void Insert(Employee object);
void Update(Employee object);
void Delete(Employee object);
// 查询方法
ArrayList FindSomeEmployees();
// 关系方法
void LoadSupervisor ( Employee emp);
void LoadSubordinates(Employee emp, int nDepth);
}
以下是客户端应用程序如何使用此代码的示例
public class NorthwindApp
{
static void Main (string[] args) {
Employee emp = new Employee();
EmployeeFactory empFactory = new EmployeeFactory();
// 让我们从 Northwind 数据库加载一名员工。
emp.EmployeeId = 10045;
empFactory.load(emp);
// empList 是 Employee 对象的集合
ArrayList empList = empFactory.FindSomeEmployees();
// subList 是 Employee 的下属集合
对象
ArrayList subList = empFactory.LoadSubordinates(emp, 1);
// supervisor 是 Employee 的主管对象
Employee supervisor = empFactory.LoadSupervisor(emp);
}
}
功能 1:灵活的对象映射
O/R 映射的一切都始于将您的对象映射到您的关系表。以下是您应该了解的此领域的一些特定功能
1. 表和视图映射:该工具应允许您将对象映射到关系数据库中的表和视图。映射到视图很重要,因为许多实际应用程序倾向于使用视图而不是表。
2. 多表映射:该工具应允许您不仅将对象映射到单个表,还可以映射到多个表,并指定这些表之间的连接。如果您的应用程序需要获取跨多个行的列表(在 Web 应用程序中很常见),您将需要此功能。
3. 命名约定:该工具应允许您在对象及其属性中使用与关系数据库中不同的命名约定。如果您的数据库表名为 t_employees,您的对象可能需要命名为 Employee。
4. 属性映射:该工具应支持多项功能
a. 主键:您的对象必须将主键与其他列区分开来。它还应允许您使用单列或多列主键。
b. 自动生成列:某些列是自动生成的(IDENTITY 或 SEQUENCE),您的对象必须包含代码来处理插入后获取生成的值。
c. 只读列:某些列不应由客户端设置,而是由系统生成其值(例如,使用 SQL Server 中的 getDate() 函数的 creation_dtime 列)。您的对象必须包含适当的代码来获取这些系统生成的值。
d. 必填列:您的对象必须在插入或更新操作时对必填列进行数据验证。这比仅仅为了获取错误消息而浪费一次数据库往返要高效得多。
e. 验证:在大多数情况下,您已在数据库列上定义了各种约束。如果能在持久化对象中进行相同的验证,就可以避免不必要的数据库往返,只为接收一条错误消息。
f. 公式字段:在许多情况下,当您从数据库获取数据时,您使用的是正则表达式而不是列(例如,AnnualSalary 对象属性可能是一个公式字段 monthly_salary * 12)。
g. 数据类型映射:有时,您希望将数据库中的一种数据类型映射到对象中的另一种数据类型。例如,datetime 类型可能会转换为字符串。您的对象必须具有自动执行此操作的双向逻辑(读取和写入)。
功能 2:使用您现有的领域对象
正如您所见,一种流行的设计模式将持久化对象分为“领域”和“工厂”对象。O/R 映射的一个重要功能是让您决定是生成领域对象和工厂对象,还是使用您现有的领域对象并仅生成了解您的领域对象的工厂对象。
有些人不想生成“领域”对象,而是手动开发它们,只生成“工厂”对象。其原因是他们的领域对象几乎在应用程序的所有子系统中都使用,因此他们不希望它们在随后的代码重新生成中频繁更改。但是,他们不介意生成“工厂”对象,因为它们的使用仅限于少数几个地方(用于加载和保存操作)。
因此,O/R 映射工具应允许您使用现有的领域对象,并仅映射和生成工厂对象。它应使用 .NET 反射读取您的领域对象定义,并在您完成映射后,以这些工厂对象使用您的领域对象来保存所有数据的方式生成工厂对象。
功能 3:事务操作(CRUD)
数据库事务允许您将多个操作分组为一个原子操作,因此所有操作要么全部成功,要么全部失败。事务操作包括创建、读取、更新和删除(也称为插入、更新、加载和删除)。每个事务操作仅对表中的一行数据执行。
您将在两种主要的事务环境中工作,您的 O/R 映射工具需要了解这两种环境,以便相应地生成代码。这些选项是:
1. COM+/MTS:Microsoft Transaction Server (MTS) 管理应用程序的所有事务。您的对象不启动、提交或回滚事务。它们只从其方法返回成功或失败,MTS 决定何时执行“BeginTrans”、“Commit”或“Rollback”。此外,您的所有工厂对象都是无状态的,因此 MTS 可以对其进行对象池化。这是一种特定的设计模式,您的 O/R 映射工具必须理解并生成您的持久化对象以符合它。此环境最常见的应用程序是 ASP.NET 应用程序和 .NET Web Services。
2. 独立:这是您的应用程序自行管理所有事务的环境。它需要知道何时执行“BeginTrans”、“Commit”和“Rollback”。您的 O/R 映射工具需要了解此环境并生成符合它的代码。这种情况最常见的是基于 Windows 窗体的客户端/服务器应用程序,它们直接与数据库服务器通信。
功能 4:关系和生命周期管理
关系型数据库的基础是表之间存在关系。同样,当您将对象映射到这些表时,您的对象也需要与其他映射对象建立相同的关系。因此,您的 O/R 映射工具必须通过让您确定要在对象中保留哪些关系来支持这一非常重要的功能。以下是您必须具备的不同类型的关系:
1. 一对一关系:在此关系中,您的对象必须包含对正好一个其他对象的引用,并且必须处理其加载和保存场景。
2. 多对一关系:这与一对一关系非常相似,您的对象必须包含对正好一个其他对象的引用,并且必须处理其加载和保存场景。
3. 一对多关系:在此关系中,您的对象必须包含相关对象的集合,并且必须处理使用加载操作加载它们,以及使用保存操作添加和删除它们。
4. 多对多关系:这是最复杂的关系,涉及数据库中的桥接表来建立关系。桥接表有两种不同的情况,如下所述:
a. 仅包含主键的桥接表:在这种情况下,桥接表只包含主键(实际上由多个外键组成)。因此,您的对象无需拥有任何桥接表属性,只需保留相关对象的集合(类似于一对多)。事实上,您对象的公共接口通常与一对多相同,但由于桥接表,底层代码有所不同。
b. 包含附加列的桥接表:这是最复杂的情况,因为桥接表具有您的对象必须处理的附加有用列。您的对象需要加载包含桥接表和相关表信息的复合对象集合。
生命周期管理功能必须包括加载主(或父)对象的能力,并通过该对象加载所有相关对象(即所有不同关系)。您还应该能够从关系中添加新创建的相关对象或删除现有相关对象。当您保存主对象时,它必须与所有关系信息一起保存(作为一个事务)。
一个好的 O/R 映射工具将允许您定义所有类型的关系,并处理生成代码中的生命周期管理。
功能 5:对象继承
正如您已经知道的,面向对象编程的一个非常重要的方面是继承。然而,关系数据库不会在关系模型中自动提供继承。但是,有许多模式可以将对象继承映射到关系数据库。一个好的 O/R 映射工具必须提供此功能。
以下是对象继承映射到关系数据库的几种方式。
1. 每个对象一张表:这是最流行和灵活的模式。在此模式中,每个对象都映射到数据库中自己的表。每个基对象和其派生对象之间存在一对一关系。此关系的外键保存在派生对象中。它是最灵活的,因为无需更改任何现有表的结构,我们就可以不断向继承层次结构添加内容。但是,对于加载基对象和派生对象而言,它的效率不高,因为每个对象都进行了单独的“加载”操作。
2. 所有对象一张表:在此模式中,基对象和所有派生对象都表示在数据库中的一张表中。此表包含表示所有对象属性的列。它对于加载和保存数据效率最高,但限制很大,因为向继承中添加新对象需要更改数据库中现有表的结构,这是非常不理想的。
考虑到这一点,O/R 映射工具至少必须支持“每个对象一张表”的方法,如果它也能支持第二种方法,那就锦上添花了。基类和派生类的生成代码应处理以下情况:
1. 插入和更新操作:派生类必须首先要求基类执行插入或更新,然后执行自己的操作。但基类和派生类的操作都必须在一个事务中执行。
2. 删除操作:与插入和更新操作不同,删除操作首先在派生对象上执行,然后是基对象。然而,两者都必须在一个事务中完成。
3. 加载操作:派生类中的加载操作也必须调用基类上的加载,并且这两者都应该在一个事务中完成。
功能 6:静态查询和动态查询
数据库应用程序做的下一件最常见的事情是从一个或多个表中检索数据行。应用程序通过使用 SQL 查询(SELECT 语句)来完成此操作。然而,面向对象应用程序希望获取对象的集合,而不是行。因此,O/R 映射工具必须为您提供一种创建返回对象集合的查询的方法。
静态查询是在编译时定义的,运行时唯一改变的是参数值。这些查询可以预编译并高效运行。因此,O/R 映射工具必须允许您将静态查询定义为对象的方法,并指定这些查询是否接受任何运行时参数。
另一方面,动态查询是那些在运行时创建查询或其条件的查询。这些查询无法预编译,必须作为“动态 SQL”运行。然而,这些查询的好处是它们允许您的应用程序中出现即席搜索操作的情况,并根据用户输入确定查询应该是什么样子。这些查询也需要作为方法提供给您的对象,但具有灵活性,您可以在运行时指定“WHERE 子句”和“ORDER BY 子句”。
功能 7:存储过程调用
存储过程在高事务环境中变得非常流行,因为它们允许您将所有 SQL 放入 DBMS 中并以编译形式存在。因此,您的 SQL 不必在运行时编译,因为这是一个非常昂贵的过程。O/R 映射工具必须支持以下两种存储过程情况:
1. 现有存储过程:第一种情况是您已经在 DBMS 中拥有自定义存储过程,并且希望您的持久化对象调用它们。在这种情况下,O/R 映射工具必须允许您在对象中定义可以调用存储过程的方法。它还必须支持不同的参数类型(in、out、in/out),以及存储过程是否返回记录集。如果存储过程返回记录集,则对象必须将此数据返回给其客户端。
2. 生成存储过程:第二种情况是,所有 SQL(动态查询除外)将作为对象关系映射的结果生成,并作为存储过程放入 DBMS 中,并且您的对象代码会生成以调用这些存储过程。如果您没有为所有 SQL 生成存储过程,它将作为“动态 SQL”放入您的对象源代码中。
功能 8:对象缓存
如果您的应用程序事务密集并支持高流量,那么您真的离不开内置在应用程序中的有效缓存。Microsoft 提供了 ASP.NET Cache 对象,但它不足以应对集群环境,因为您的应用程序在多台服务器上运行,需要一个也集群的缓存。然而,.NET 中有可用于集群环境的商业缓存解决方案。
无论您使用哪种缓存产品,都必须确保您的持久化对象从适当的位置发出缓存调用。并且,您的 O/R 映射工具应提供生成代码的能力,该代码可以向一个或多个领先产品发出缓存调用。
理想情况下,您的 O/R 映射工具应该允许您指定要缓存哪些对象以及不缓存哪些对象。最常见的情况是事务对象(单行对象)被缓存。但是,您也可以缓存整个集合甚至相关对象。
功能 9:生成代码的定制
您总会遇到需要定制生成代码的情况。但是,如果您更改了生成代码,下次再次生成代码时很可能会被覆盖。而且,由于软件开发是一个迭代过程,您将不得不多次生成代码。
此外,您编写的任何自定义代码都必须在生成代码运行时被调用。并且,它还必须能够控制生成代码的后续执行。例如,如果您的自定义代码在执行“插入”之前被调用,并且您发现有问题,您应该能够阻止“插入”实际发生。
为了防止您的代码被覆盖,O/R 映射工具必须允许您将代码标记为“安全代码”,这样在将来的代码重新生成中就不会被覆盖。为了确保您的自定义代码无缝调用,O/R 映射工具需要支持“挂钩”的概念,这些挂钩是从生成代码中的战略位置进行的调用,这些“挂钩”返回的结果代码决定了接下来会发生什么。或者,O/R 映射工具需要允许您派生生成代码,然后使用多态性实际运行您的代码而不是生成代码。然后您可以决定是否调用“基类代码”。
功能 10:模板定制
一个好的 O/R 映射工具很可能使用代码模板来确定如何生成代码。O/R 映射工具将其拥有的模板与您的对象映射输入和数据库模式信息的组合相结合,以精确确定如何生成代码。
由于 O/R 映射工具是从模板生成代码,如果它允许您修改这些模板(或添加新模板),以便您可以影响生成代码的外观,那将是非常棒的。
一个非常简单的例子是,当您希望在每个源代码文件中加入自己的版权头。如果您可以在代码模板文件中插入此头,那么下次生成代码时它将自动使用。您还应该能够编写自己的代码模板(尽管这仅适用于高级用户),并让 O/R 映射工具使用您的模板,但以它一贯的方式完成所有其他操作。
结论
您应该认真考虑使用 O/R 映射工具,因为它将为您节省大量的开发和测试时间。在评估哪种工具最适合您时,您应该知道要寻找什么。我希望本文能帮助您更好地理解 O/R 映射。
伊克巴尔·汗是 AlachiSoft (http://www.alachisoft.com/) 的联合创始人,AlachiSoft 是一家领先的 .NET O/R 映射工具 TierDeveloper 和 .NET 集群对象缓存 NCache 的提供商。您可以通过 iqbal@alachisoft.com 与他联系。