65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (14投票s)

2005 年 6 月 20 日

6分钟阅读

viewsIcon

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 的六个步骤:

  1. 创建领域模型(UML 图,或在脑海中进行文本描述)。
  2. 为 NEO 创建 XML 格式的领域描述符。
  3. 为领域模型生成 C# 类和 SQL 查询。
  4. 将 C# 类包含到项目中。
  5. 创建数据库并执行 SQL 查询。
  6. 使用 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、继承问题等等。

NHibernate 与 NEO:面对面。第一部分 - CodeProject - 代码之家
© . All rights reserved.