NHibernate 与 NEO:面对面。第一部分





4.00/5 (14投票s)
2005 年 6 月 20 日
6分钟阅读

124630
NHibernate 与 NEO 系列的第一篇文章。NEO O/R 映射工具简介。
引言
在过去一年中,.NET 平台上出现了许多开源的 O/R 映射框架。它们中的大多数都很粗糙,只有少数几个可以用于实际开发。在开源解决方案中,NEO 和 NHibernate 在 O/R 映射领域名列前茅。这就是我们将在本系列文章中面对面比较它们的原因。
在本文中,我们将介绍 NEO 框架。我对 NEO 有很好的经验,因为我们在 TargetProcess 项目——一个基于 ASP.NET 的敏捷项目管理系统中使用了它。该项目已经相当成熟,这是来自一个真实生产项目的真实经验。
简短介绍
NEO
NEO 是一个最初为 .NET 创建的框架。它非常简单且功能齐全,可以解决几乎所有常见的 O/R 映射问题。请访问网站:codehaus。
NHibernate
NHibernate 是非常流行的基于 Java 的 Hibernate 框架的 .NET 移植。这些年有一个非常明显的趋势,就是将 Java 世界中所有好的工具移植到 .NET。Hibernate 被广泛使用,并已发展成为最强大的 O/R 映射解决方案之一。请访问:NHibernate。
NEO 框架
几乎所有 O/R 映射工具的使用方式都非常统一,只存在细微的差异。以下是开始使用 NEO 的六个步骤:
- 创建领域模型(UML 图,或在脑海中进行文本描述)。
- 为 NEO 创建 XML 格式的领域描述符。
- 为领域模型生成 C# 类和 SQL 查询。
- 将 C# 类包含到项目中。
- 创建数据库并执行 SQL 查询。
- 使用 NEO 框架处理持久化对象。
创建映射
任何 O/R 映射工具都与领域模型协同工作。有些更接近数据库,而另一些则更接近领域模型。NEO 处于中间位置。您可以创建数据库并生成领域模型描述符文件,也可以创建领域模型描述符,然后生成数据库。我更喜欢第二种方式。
让我们来看一个非常简单的领域模型。这是一个非常基本的缺陷跟踪系统。您可以添加项目、添加缺陷、将其分配给用户,并向缺陷添加评论。这是类图:
现在我们应该将这个漂亮的 UML 图转换为 NEO 语言——XML 格式的模型描述符。我们从项目类映射开始。如您所知,领域模型中的每个类在数据库中都对应一个表(我们暂时不考虑继承)。因此,`Project` 类的所有对象都将存储在 `projects` 表中。
<?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?>
<!DOCTYPE database SYSTEM "Resources/norque.dtd">
<database name="tp" package="Neo.Web.Model"
defaultIdMethod="native" defaultJavaNamingMethod="underscore">
<table name="projects" javaName="Project">
. . .
</table>
</database>
嗯,也许您对基于 C# 的框架中使用的 `javaName` 属性有些困惑。但是,正如您可能已经猜到的那样,存在一个合理的解释。事实是,NEO 使用 Torque O/R 映射工具的 DTD,该工具是用 Java 编写的,并且用于 Java。
我们已经映射了类,现在我们应该对类属性做同样的事情。我们的 `Project` 类有四个属性:
名称
,描述
,StartDate
,ProjectId
在数据库中,`ProjectId` 将是一个自增的主键列。所有其他属性的映射都非常简单。
<table name="projects" javaName="Project">
<column name="project_id" primaryKey="true"
autoIncrement="true" required="true" type="INTEGER" />
<column name="name" required="true"
type="VARCHAR" size="150" />
<column name="description" required="false"
type="VARCHAR" size="600" />
<column name="start_date" required="true"
type="DATE" />
</table>
让我们跳过 `User` 类的映射,因为它几乎相同,直接看 `Bug` 类。根据领域模型,每个项目都有一系列 Bug。这是一个一对多的关系,在关系数据库领域,它由一个简单的外键表示。
<table name="bugs" javaName="Bug">
<column name="bug_id" primaryKey="true" autoIncrement="true"
required="true" type="INTEGER" />
<column name="name" required="true" type="VARCHAR" size="250" />
<column name="description" required="false"
type="VARCHAR" size="2500" />
<column name="effort" required="false" type="DECIMAL" />
<column name="status" required="true" type="INTEGER" />
<column name="project_id" required="true" type="INTEGER" />
<foreign-key foreignTable="projects" name="Project"
onDelete="cascade">
<reference local="project_id" foreign="project_id" />
</foreign-key>
</table>
所有 `Bug` 对象都存储在 `bugs` 表中,`bug_id` 是主键,`project_id` 是 `projects` 表中的外键。
<foreign-key foreignTable="projects" name="Project" onDelete="cascade">
`Name` 属性设置关系的名称。在这种情况下,每个 `Bug` 对象都将有一个对 `Project` 对象的引用,在代码中您将能够像这样编写:
String projectName = bug.Project.Name;
<reference local="project_id" foreign="project_id" />
`Reference` 设置外键的列。在这种情况下,`project_id` 列应存在于 `bugs` 表和 `projects` 表中。
如果我们希望在 `Project` 对象中拥有 Bug 集合,我们应该将有关 Bug 的信息添加到 `Project` 类定义中:
<table name="projects" javaName="Project">
. . .
<iforeign-key foreignTable="bugs" name="Bugs" onDelete="cascade">
<ireference local="project_id" foreign="project_id" />
</iforeign-key>
</table>
public ICollection GetBugs() { return new BugList(this.Bugs); }
最后,完整的映射将是这样的:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?>
<!DOCTYPE database SYSTEM "Resources/norque.dtd">
<database name="tp" package="Neo.Web.Model" defaultIdMethod="native"
defaultJavaNamingMethod="underscore">
<table name="projects" javaName="Project">
<column name="project_id" primaryKey="true" autoIncrement="true"
required="true" type="INTEGER" />
<column name="name" required="true" type="VARCHAR" size="150" />
<column name="description" required="false" type="VARCHAR"
size="600" />
<column name="start_date" required="true" type="DATE" />
<iforeign-key foreignTable="bugs" name="Bugs" onDelete="cascade">
<ireference local="project_id" foreign="project_id" />
</iforeign-key>
</table>
<table name="users" javaName="User">
<column name="user_id" primaryKey="true" autoIncrement="true"
required="true" type="INTEGER" />
<column name="name" required="true" type="VARCHAR" size="150" />
<column name="login" required="true" type="VARCHAR" size="50" />
<column name="password" required="true" type="VARCHAR"
size="50" />
</table>
<table name="bugs" javaName="Bug">
<column name="bug_id" primaryKey="true" autoIncrement="true"
required="true" type="INTEGER" />
<column name="name" required="true" type="VARCHAR" size="250" />
<column name="description" required="false" type="VARCHAR"
size="2500" />
<column name="effort" required="false" type="DECIMAL" />
<column name="status" required="true" type="INTEGER" />
<column name="project_id" required="true" type="INTEGER" />
<column name="user_id" required="true" type="INTEGER" />
<foreign-key foreignTable="projects" name="Project"
onDelete="cascade">
<reference local="project_id" foreign="project_id" />
</foreign-key>
<foreign-key foreignTable="users" name="Owner">
<reference local="user_id" foreign="user_id" />
</foreign-key>
<iforeign-key foreignTable="comments" name="Comments"
onDelete="cascade">
<ireference local="bug_id" foreign="bug_id" />
</iforeign-key>
</table>
<table name="comments" javaName="Comment">
<column name="comment_id" primaryKey="true" autoIncrement="true"
required="true" type="INTEGER" />
<column name="text" required="false" type="VARCHAR" size="1000" />
<column name="date" required="true" type="DATE" />
<column name="user_id" required="true" type="INTEGER" />
<column name="bug_id" required="true" type="INTEGER" />
<foreign-key foreignTable="users" name="User">
<reference local="user_id" foreign="user_id" />
</foreign-key>
<foreign-key foreignTable="bugs" name="Bug" onDelete="cascade">
<reference local="bug_id" foreign="bug_id" />
</foreign-key>
</table>
</database>
生成模型类
现在我们已经映射了所有四个类,并准备好为它们生成 SQL 和 C# 代码。NEO 有一个代码生成工具 `neo.exe`,它可以在 `[neo root folder]\src\Tools\CmdLineTool` 文件夹中找到。创建一个包含所有所需命令的 BAT 文件很方便。
neo.exe -r Resources -f -sql true -sqldrop true -o Sql model.xml
neo.exe -r Resources -user true model.xml
neo.exe -r Resources -support true -o Base model.xml
第一个命令生成 SQL 查询并将它们放入 `Sql` 文件夹。使用这些查询,您可以为示例应用程序创建数据库。第二个命令生成可以由用户修改的模型类(NEO 将来不会重新生成)。第三个命令创建基本模型类,当模型更改时,NEO 将重新生成它们。最后,我们将得到如下图所示的结构:
执行 BAT 文件后,我们应该将新类包含到我们的项目中并更改命名空间。
现在我们应该创建一个数据库并执行从 `Sql` 文件夹生成的 `model.sql` 脚本。
连接到数据库
让我们完成准备工作的最后一部分——连接到数据库。首先,创建 `DataStoreFactory` 类,它将创建数据库连接。`Web.config` 中将有两个参数。
<add key="ConnectionString"
value="data source=(local);initial catalog=ormapping;uid=sa;pwd=" />
<add key="DatabaseServer" value="Neo.SqlClient.SqlDataStore,Neo" />
`DataStoreFactory` 只有一个 `Create` 方法:
public IDataStore Create()
{
string className =
ConfigurationSettings.AppSettings["DatabaseServer"];
string conn =
ConfigurationSettings.AppSettings["ConnectionString"];
return (IDataStore)Activator.CreateInstance(
Type.GetType(className + ",Neo"),new object[] {conn});
}
是时候享受 NEO 框架的乐趣了。应用程序的第一个页面将添加一个新项目并显示项目列表。哦,我们还没有完成连接操作。好的,这很简单。只需实例化 `IDataStore` 并创建 `ObjectContext` 的实例。实际上,`ObjectContext` 非常有趣,它是一个工作单元模式。它存储所有已加载对象的更改,并且是所有 NEO 操作的必需品。
// create data store
DataStoreFactory store = new DataStoreFactory();
IDataStore dataStore = store.Create();
// create context for changes tracking
context = new ObjectContext(dataStore);
持久化
页面上有一个网页表单,像往常一样,有几个文本框和一个按钮。当用户填写表单并点击按钮时,一个新项目会添加到数据库中。以下事件处理程序将执行所有操作:
private void btnAddProject_Click(object sender, System.EventArgs e)
{
// create new project
ProjectFactory projectFactory = new ProjectFactory(context);
Project project = projectFactory.CreateObject();
// set project properties
project.Name = txtName.Text;
project.Description = txtDescription.Text;
project.StartDate = Convert.ToDateTime(txtStartDate.Text);
// save new project into the database
context.SaveChanges();
}
首先实例化 `ProjectFactory` 类。该类负责所有与项目相关的操作:创建、检索、删除等等。然后我们创建一个新项目。请注意,这个新项目目前还不在数据库中。在下一步中,我们从表单字段设置所有项目属性。最后,我们将 `ObjectContext` 中的所有更改保存到数据库中。在我们的例子中,NEO 将生成 `INSERT
` 查询。
现在需要从数据库中获取所有项目并将其显示在列表中。`Page_Load` 处理程序中的代码非常简单:
private void Page_Load(object sender, System.EventArgs e)
{
// create data store
DataStoreFactory store = new DataStoreFactory();
IDataStore dataStore = store.Create();
// create context for changes tracking
context = new ObjectContext(dataStore);
// retrieve all projects from database
ProjectList projects = new ProjectFactory(context).FindAllObjects();
// fill datagrid
lstProjects.DataSource = projects;
lstProjects.DataBind();
}
我们再次使用 `ProjectFactory` 从数据库中检索所有项目,并使用该列表作为 `lstProjects` DataGrid 的 `DataSource`。就是这样。
结论
您还记得以前使用动态 SQL 或存储过程进行数据库操作的时代吗?NEO 或任何其他优秀的 O/R 映射解决方案都让生活更轻松,代码更简洁。非常多!
这只是 NEO 的快速入门。在下一篇文章中,我们将探讨更复杂的主题,例如复杂查询、`ObjectContext` 处理的可能架构解决方案、分离式数据库和基于 NEO 的 TDD、继承问题等等。