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

使用 NHibernate 的对象关系映射 (ORM) – 第 3 部分(共 8 部分):编码一对多实体关联

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (8投票s)

2012 年 10 月 7 日

CPOL

23分钟阅读

viewsIcon

57127

包含 8 部分的完整系列文章,展示使用 NHibernate 进行一对一、多对一、多对多关联映射,使用 NHibernate 处理集合,使用 NHibernate 处理继承关系,使用 NHibernate 处理延迟初始化/获取。

文章系列

引言

实体之间最常见也是最重要的关联是“一对多”关联。在本系列文章的第 3 部分中,我们将讨论一对多关联,并将可选的一对多关联留到下一部分。第一个问题是,一对多和可选一对多有什么区别?简单地说,可选的一对多允许关联的多重性中包含值“0”(零)。下一个问题是,这种区别是否至关重要?从 ORM 的角度来看,绝对的。这非常重要,因为可选的一对多关联会导致可为空的外键列,因此需要以不同的方式处理,以避免空值,而空值始终是数据库存储数据质量的首选。

为了强调一对多和可选一对多之间的区别,本文将给出两个例子。首先,将展示一个电子商务场景中的一对多关联示例,其中集合端使用 `<set>`。如果将其映射为可选一对多关联会更好,但这里将简单地映射为一对多。因此,外键列中会有大量的空值。在本系列文章的下一部分(即第 4 部分)中,将对其进行改进,映射为可选一对多,从而消除所有空值,以提高存储数据的质量和可靠性。本文还将展示另一个示例,该示例不会产生空值,因为它是正确映射的一对多关联场景。第二个示例的集合端使用 `<list>`。

背景

首先,在本系列文章的第 1 部分中,已经展示了在 ORM 中,当对象之间的一对一关联映射到它们对应的表时,一个表的主键被作为另一个表的外键发布。但在 ORM 中,一对多关联的映射方式有所不同。图 1 展示了对象之间的一对多关联是如何映射的。

图 1

图 1 显示,在 ORM 中,将对象之间的一对多关联映射到各自的表时,一对多关联的“一”端表的主键被作为外键发布到一对多关联的“**多**”端表(为便于描述,在本系列文章中,我们将一对多关联的集合类端称为“**多**”端)。这种关联不可能以其他方式映射。**为什么**?如果从相应表的行和列的角度来思考,答案很容易理解。答案非常简单,如下所述。

当我们说表 A 和表 B 之间存在一对多关联时,简单来说,我们指的是表 A 中的一行将与表 B 中的多行相关联,在数据库中,这种关联是通过将主键作为外键列发布在表之间建立的。因此,如果外键从“多”端表(即表 B)的主键**错误地**发布到“一”端表(即表 A),那么表 A 中每一行的该外键列值将有多个值对应于表 B 中每一行的主键值,与表 A 中的特定行关联(因为表 A 和表 B 之间的关联是一对多,这意味着简单来说,表 A 中的一行与表 B 中的多行相关联)。但是,在关系数据库中,我们不能为一列分配多个值。因此,从逻辑上讲,将对象之间的一对多关联映射到它们对应的表的唯一方法是将“一”端表的主键作为外键发布到“多”端表。请通过下面的 NHibernate 示例来查看实际操作。

使用代码

继续电子商务场景

回到我们的电子商务场景,一个一对多关联的例子是:“当顾客提交订单进行审批时,系统会根据订单中给出的产品描述检查商品可用性。如果商品可用,则将商品添加到订单中。这针对每个产品描述进行。如果商品不可用,则将其添加到订单中不可用商品的列表中。最后,可用商品和不可用商品的列表以及应付总金额会发送给顾客。一旦顾客提交付款并确认订单,库存中的商品就会被标记,表示它们已被订购。顾客支付的商品会被添加到 PAYMENTAPPROVEDORDER 中。”

这里只对最后两句话感兴趣,因为它包含了 `ITEM` 和 `PAYMENTAPPROVEDORDER` 之间的多对一关联。`Item` 和 `PaymentApprovedOrder` 都是实体类。这里 `Item` 代表库存中的商品,而不是通常的账单订单中的订单项,因为在电子商务中,您不会像现实世界那样选择特定的商品进行支付和带走。您只是根据产品描述(如果库存可用)选择要购买的商品。因此,我们将尝试捕捉这种电子商务场景。因此,在将 `Order` 实例提交到系统并进行支付之前,我们不会将订单项与 `Order` 持久化,此时 `Order` 成为 `PaymentApprovedOrder` 并附加到已支付的顾客(本文第二个示例的场景 - 顾客及其已支付的订单)。当支付提交到系统时,`Inventory` 中该订单的 `Item` 实例会通过一个标志进行更新,表示 `Item` 已被订购,之后会创建一个 `PaymentApprovedOrder`,其中包含指向该订单中所有 `Item` 实例的链接。本文第一个示例讨论的主题正是 `PaymentApprovedOrder` 和 `Item` 实例之间的这种链接。稍后,当商品交付时,该特定订单的所有商品将从库存 `Items` 集合中移除并添加到 `DeliveredItems` 中。这是一个简单的文章示例场景。

回到这里的讨论,所以很明显 `Item` 将拥有独立的生命周期,因此它是一个实体。`PaymentApprovedOrder` 和 `Item` 之间的关联是可选的一对多。为什么会这样?为什么不简单地一对多?虽然 `PaymentApprovedOrder` 肯定需要至少一个 `Item`,但 `Item` 必须被订购有什么必要性吗?它可能是一个从未被订购的 `Item`,在这种情况下,它根本不与订单关联,只是存在于库存中而没有销售。因此,`PaymentApprovedOrder` 和 `Item` 之间的关联是可选的一对多。但是,我们将把它映射为简单的一对多,数据库中外键列值会有大量的空值,然后将在下一篇文章中改进它,通过将其映射为可选的一对多来避免这些空值。本文中的下一个示例场景将是一个典型的一对多场景,它将完全避免这些空值。

编码一对多

在上一篇文章中,清楚地展示了某些集合(如 `<list>`)可以保留顺序信息,而某些集合(如 `<set>`)可能没有顺序信息。在本文中,将展示对于像 `<list>` 这样的集合,当它是双向关联时,如何进行映射以保留顺序信息,以及如果操作不正确会发生什么。因此,这是从本文中推断出的一个值得注意的附加点。首先,我们将考虑 `PaymentApprovedOrder` 和 `Item` 之间一对多关联的示例,其中“**多**”端映射为 `<set>`。接下来,我们将考虑来自电子商务场景的另一个示例,其中包含 `<list>`,并查看映射上的差异,以确保 `<list>` 中捕获的顺序信息得到正确存储。

带 <SET> 集合映射的一对多

请看下面的图 2。它显示了 `PaymentApprovedOrder` 和 `Item` 之间的一对多双向关联的映射。`Item` 实例的集合使用 `<Set>` 集合进行映射。在 C# 代码中,集合的声明始终通过接口进行,并将通过使用正确的实现 C# 类进行定义。阅读过本系列文章第 1 部分和第 2 部分的开发人员将知道如何解释该图和所示的彩色箭头。如果需要解释该图的帮助,请阅读本系列文章的第 1 部分和第 2 部分。在图 2 中,请看三个紫色箭头。它显示列“PAYMENTAPPROVEDORDERID”是关联的“**一**”端表(即 PAYMENTAPPROVEDORDER 表)的主键,并作为外键列发布到“**多**”端表(即 ITEM 表)。这符合映射一对多关联的预期,正如之前在**背景**部分解释 ORM 基础时所展示的。由于关联是双向的,列 PAYMENTAPPROVEDORDERID 被映射两次以完成链接的两端,如图 2 中跟随紫色箭头所示。

图 2

此关联的一端是 `PaymentApprovedOrder` 类。在 *PaymentApprocedOrder.cs* C# 文件中,`Item` 集合属性声明如下

public virtual ISet<Item> PaidOrderItems { get; set; }

注意 - 上面的 `ISet<>` 来自 `Iesi.collections.generic` 命名空间。在 *PaymentApprovedOrder.hbm* 映射文件中,上述集合映射为

<set name="PaidOrderItems" inverse="true" cascade="save-update">      
      <key column="PAYMENTAPPROVEDORDERID"  not-null="true"/>
      <one-to-many class="Item" />
</set>

如果您将此与第 2 部分中展示的值类型集合的 `<set>` 映射进行比较,您会看到许多差异,这些差异有助于更好地理解 NHibernate 映射。第一个差异是缺少用于表示集合元素的 `<element>`,它用于值类型集合。接下来,表未命名。NHibernate 知道当我们看到 `<set>` 内部没有 `<element>` 时,我们正在处理实体关联。此外,我们不使用 `<set>` 中的 `<element>` 标签,而是使用 `<one-to-many>` 这样的关联标签。这是向 NHibernate 发出的信号,表明它正在处理实体关联,并使用 `<one-to-many>` 标签中 class 属性中指定的表,在本例中为 `class="Item"`。因此,NHibernate 将使用 Item 类映射文件中指定的 ITEM 表,并使用 PAYMENTAPPROVEDORDERID 列作为外键列。另请注意,我们明确指定了“cascade”属性。每个人都熟悉数据库和 ADO.NET 中的 cascade 属性,其功能相同,即当 `PaymentApprovedOrder` 保存或更新到其表时,`<set>` 表示的集合也必须自动保存或更新到其表。这没有什么特别的,只是维护了通常的父子关系。当拥有父对象保存或更新时,级联保存更新函数会自动应用于拥有的子对象,而无需单独显式执行。但最有趣和最重要的是外键列“PAYMENTAPPROVEDORDERID”中的 `not-null=true`(可以不在这里,但我放置它是为了解释一个重要的 NHibernate 映射概念),我们将在稍后检查存储在数据库中的测试数据和结果时进行探讨。由于这是一个双向关联,让我们看看关联的另一侧。在 *Item.cs* C# 文件中,用于存储与 `PaymentApprovedOrder` 关联的属性如下

public virtual PaymentApprovedOrder PaidOrder { get; set; }

在 Item.hbm 映射文件中,Item 和 PaymentApprovedOrder 之间的多对一关联映射为

<many-to-one class ="PaymentApprovedOrder" name ="PaidOrder" 
   column ="PAYMENTAPPROVEDORDERID"/>

`Item` 和 `PaymentApprovedOrder` 之间的关联完全按照预期通过 `<many-to-one>` 关联进行映射,并且使用属性 `column` 来标识维护关联的外键列,如上面的代码片段所示。现在请看下面的图 3。它显示了 `PaymentApprovedOrder` 和 `Item` 之间双向一对多关联的映射两端。如果您查看 *PaymentApprovedOrder.hbm* 映射文件中的 `<set>` 定义(图 3 的左侧)和 *Item.hbm* 映射文件中的 `<many-to-one>` 关联定义(图 3 的右侧),您会发现两者都映射到由橙色箭头所示的相同列 PAYMENTAPPROVEDORDERID。

图 3

由于关联是双向的,同一个外键列 PAYMENTAPPROVEDORDERID 被映射了两次,如图 3 所示。在 C# 代码中,双向关联的两端也必须链接起来。因此,在定义 `PaymentApprovedOrder` 类的 *PaymentApprovedOrder.cs* 文件中(如下面的代码片段所示——只显示了类的相关部分),请查看 `AddPaidItem(Item item)` 方法,该方法设置了一对多关联的两端。

public class PaymentApprovedOrder
{
    public virtual long PaymentApprovedOrderId { get; set; }
    public virtual ISet<Item> PaidOrderItems { get; set; }
    public virtual void AddPaidItem(Item item)
    {
        //SET THE REFERENCE FOR PAYMENTAPPROVEDORDER
        //IN ITEM OBJECT TO THIS i.e THE PAYMENT APPROVED
        //ORDER INSTANCE TO WHICH THE item IS ADDED".
        // THIS IS FIRST END OF THE
        // One-to-Many ASSOCIATION - THE "ONE" END
        item.PaidOrder = this;
        //ADD "item" TO THE SET PaidOrderItems
        //OTHER END OF ASSOCIATION - THE "MANY" END
        PaidOrderItems.Add(item); 
    }
}

因此,当 `AddPaidItem` 的 C# 代码执行时,`PaymentApprovedOrder` 和 `Item` 之间一对多关联的两端都建立了。但是,正如上面图 3 所示,在数据库表中,关联的两端都映射到同一列,即 PAYMENTAPPROVEDORDERID,它是“一”端表(即 PAYMENTAPPROVEDORDER 表)的主键,并作为外键列发布到“多”端表(即 ITEM)。在双向关联中,同一列被映射两次可能会导致 NHibernate 生成自动插入、删除和更新 SQL 语句时出现问题,因为当链接在 C# 对象代码中管理时,同一列将被插入或更新两次,这种双重更改可能导致冲突、约束违反或重复。因此,双向链接的一端必须在映射文件中停用,以便插入和更新语句不会为两端生成,而只为一端生成。有两种方法可以做到这一点。

第一种方法是在 `<set>` 等集合中设置 `INVERSE=TRUE` 属性,如图 3 左侧所示。现在,关联的集合端,即 `<set>` 端被指示为对 NHibernate 不活跃,以便当对该端(即集合端对象链接)进行更改时,NHibernate 不会自动生成插入和更新 SQL 语句,而与此相反的“一”端(如图 3 右侧所示)成为自动生成 SQL 语句以对应对象更改的活跃端。因此,NHibernate 仅在链接的“一”端更改时才会自动生成 SQL 语句。

在上面代码片段中所示的 `PaymentApprovedOrder` C# 类的 `AddPaidItem(Item item)` 方法中,该方法建立了 `PaymentApprovedOrder` 和 `Item` 之间一对多关联的 C# 代码两端,NHibernate 仅在调用 "item.PaidOrder=this" 时才生成更新或插入到数据库的 SQL 语句。使用集合端(即 `PaidOrderItems.Add(item)`)不会发生任何事情,因为它已通过映射文件中的 `<set>` 的 `Inverse=true` 属性被指示为不活跃使用,以供 NHibernate 自动生成 SQL 语句。这意味着,仅仅将一个 Item 实例添加到 `PaymentApprovedOrder` 中由 `PaidOrderItems` 属性表示的 Item 集合中,该 Item 不会被添加到数据库中。因此,设置 `PaidOrderItems.Add(item)` 不会将 Item 添加到数据库,因为它已通过映射文件中的 `<set inverse="true">` 向 NHibernate 指示不使用集合端来自动生成插入和更新 SQL 语句。Item 实例只有在设置了表示关联“一”端的 `Item` 的 `PaidOrder` 属性时,NHibernate 才会通过自动生成的插入和更新 SQL 语句将其添加到数据库中。因此,只有在执行 `item.PaidOrder=this` 时,Item 才会被添加到数据库中,因为 NHibernate 仅为“一”端生成自动的插入和更新 SQL 语句。

因此,当实体类之间存在双向关联时,为了避免任何冲突,关联的一端必须在 NHibernate 映射文件中停用,而其中一种方法是在映射文件中,对于没有排序信息的集合(如 `<set>`),指定 `INVERSE=TRUE`。现在,为什么我们不能对具有排序信息的集合(如 `<list>`)使用 `INVERSE=TRUE` 呢?这个问题的答案在下面描述,并且非常有趣。

为什么当集合(例如 `<list>`)在双向关联中使用时,我们不能对具有排序信息的集合映射使用 `INVERSE=TRUE` 属性?当我们使用 `inverse=true` 属性映射一个集合时,因为该集合在双向关联中使用,NHibernate 实际上不认为定义了 `inverse=true` 属性的集合可用于 C# 代码中相应集合对象发生更改时的任何进一步自动 DML 语句生成。只有关联的另一端才会被 NHibernate 使用。因此,它不会将 `<list inverse=true>` 映射用于进一步的使用,但不幸的是,`<list>` 的排序信息仅封装在 `<list>` 定义内部。这意味着,如果对 `<list>` 等集合使用 `inverse=true` 属性,则排序或索引信息将被忽略。因此,如果设置 `<list inverse=true>`,排序信息将丢失。因此,带有 `<list>` 和其他具有排序信息的集合的双向一对多关联映射必须以不同的方式处理,这将在下一节中以 `<list>` 为例进行讨论。

图 4 - <SET> 集合映射的测试客户端代码

请阅读图 4 中的测试代码,并查看图 5 中测试代码生成的 ITEM 表行。请注意图 5 中的橙色箭头。图 5 显示了 ITEM 类的 <set> 集合映射(图 5 的下半部分)和测试代码生成的 ITEM 表行(图 5 的上半部分)。<SET> 映射清楚地说明 ITEM 表的外键 PAYMENTAPPROVEDORDERID 为 NOT NULL(由橙色箭头指示)。因此,在未将 PAYMENTAPPROVEDORDERID 设置为非空值的情况下,无法将 ITEM 添加到 ITEM 表中。然而,我们看到在图 5 的上半部分,有三行 PAYMENTAPPROVEDORDERID 为 NULL(由橙色箭头指示)。这种 NOTNULL=TRUE 约束违反是如何可能的呢?不要错误地认为这是 inverse=true 属性设置的副作用。它与 inverse=true 属性设置无关。答案是 not-null=true 应该在关联的 ONE 端也定义才能生效,而不仅仅在集合映射中。在此示例中,not-null=true 必须在 Item.hbm 映射文件中定义,如下所示:<many-to-one class="PaymentApprovedOrder" name="PaidOrder" not-null=true> 标签。但不幸的是,如果您这样做,那么约束将触发,并且除非 Item 属于 Order 的一部分,否则无法添加 Item,而这并不是我们在电子商务场景中想要的。我们希望 Item 实例在没有订单的情况下存在于库存中,并且当订单生成时,Item 将具有针对 PaymentApprovedOrder 的适当引用集,并且在 Item 中设置一个标志,表示它已订购。这就是为什么它应该作为可选一对多处理,而不仅仅是简单的一对多。这清楚地有助于理解如果一对多和可选一对多使用不当会产生的问题。

图 5

总结本节,重要的是仔细查看图 5 上半部分所示的 ITEM 表行。注意外键“PAYMENTAPPROVEDORDERID”中的空值。这些空值之所以存在,是因为 `PaymentApprovedOrder` 和 `Item` 之间的关联是可选的一对多(一个商品可能存在于库存中,从未被购买,因此其 paidorder 引用为空),但在本示例中将其映射为一对多,以突出外键列中存在空值的问题。在本系列文章的第 4 部分中,我们将 `PaymentApprovedOrder` 和 `Item` 之间的这种关联正确地映射为可选一对多,所有这些空值都将消失。现在,在下一节中,一个一对多关联将被正确映射,您将看到外键列中没有空值,因为它正确地符合该场景。

最后,请注意前两篇文章包含了 `IRepository`、`DBRepository`、`Payment` 类的代码。图 4 显示了测试代码。图 6 显示了 PaymentApprovedOrder 的代码和映射文件。图 7 显示了 Order 的代码和映射文件。

图 6 - PAYMENTAPPROVEDORDER 及其映射代码

图 7 - 订单及其映射文件。OrderItems 未在映射文件中映射,因为它未持久化到数据库(原因如前所述,我们使用 PaymentApprovedOrder 来链接支付和订单项)。NHibernate 具有这种灵活性。

带 <LIST> 集合映射的一对多

`<list>` 集合映射包含排序信息。因此,对于具有 `<list>` 映射的双向关联,由于前面解释的原因,不能使用 `<list inverse="true">` 映射关联链接的一端。`<list>` 映射必须以不同的方式完成,这里将通过电子商务场景中的一个不同示例进行展示。“顾客将能够知道他已付款的订单。订单将始终标识已下订单的顾客。” `Customer`、`Order` 和 `PaymentApprovedOrder` 都是实体类。我们只在付款后将订单作为 `PaymentApprovedOrder` 进行持久化,并将其订单的 `Item` 集合与 `PaymentApprovedOrder` 关联,如前面的示例所示。`PaymentApprovedOrder` 始终链接到一个顾客,但在线商店中的顾客可能只有一个登录帐户,根本没有下任何订单,或者可以下多个已付款的订单。但无论如何,已付款订单不能没有顾客而存在。因此,`Customer` 和 `PaymentApprovedOrder` 之间的关联肯定是一对多,因为 `PaymentApprovedOrder` 只能由顾客完成。在这里,对于“多”端集合,我们将使用 `<LIST>`。另请注意,这里外键映射中不会有任何空值,因为 `PaymentApprovedOrder` 不会没有顾客而存在。

图 8 显示了 `Customer` 和 `PaymentApprovedOrder` 类及其映射。阅读过本系列文章第 1 部分和第 2 部分的开发人员将知道如何解释该图和所示的彩色箭头。如果需要解释该图的帮助,请阅读本系列文章的第 1 部分和第 2 部分。

图 8

请看图 8 中的紫色箭头。它清楚地显示了“一”端表(即 CUSTOMER 表)的主键 CUSTOMERID 如何作为外键发布到“多”端或集合端表(即 PAYMENTAPPROVEDORDER 表)。这符合映射一对多关联的预期,正如之前在背景部分解释 ORM 基础时所展示的。由于关联是双向的,列 CUSTOMERID 被映射两次以完成链接的两端,如图 2 中跟随紫色箭头所示。一个客户可能有多个 PaymentApprovedOrders(通过多个订单购买多个商品)。因此,在 *Customer.cs* 文件中,`PaymentApprovedOrder` 集合的声明如下:

public virtual IList<PaymentApprovedOrder> CustomerPaidOrders { get; set; }

在 *Customer.hbm* 文件中,它被映射为

<list name ="CustomerPaidOrders" cascade="save-update">
      <key column ="CUSTOMERID" not-null ="true"></key>
      <list-index column ="PAIDORDER_LIST_POSITION"></list-index>
      <one-to-many class ="PaymentApprovedOrder"/>
</list>

已经表明,当使用双向关联时,必须通知 NHibernate,这样当双向链接的两端都发生更改时,它将不会生成插入和更新 SQL 语句,因为就数据库而言,它仍然是同一个外键列,该列被映射到链接的两端两次,以实现双向关联。我们看到,在 `<set>` 集合中,通过使用 `inverse=true` 属性(`<set inverse=true...>`),当集合链接端发生更改时,我们可以阻止 NHibernate 生成插入和更新 SQL 语句,当集合在双向一对多关联中使用时。还表明,对于具有排序信息的集合(如 `<list>`),由于前面提到的原因,不能设置 `inverse=true` 属性。因此,如果您查看上面代码片段中使用的映射,该映射在 `Customer` 和 `PaymentApprovedOrder` 之间的双向一对多关联中使用了 `<list>` 集合映射,`INVERSE=TRUE` 未设置为 `<list>`。因此,必须在另一端,即“一”端,告知 NHibernate,当对该端的对象进行更改时,不要自动生成插入和更新语句。只有当客户购买并支付了包含商品的订单时,才会创建 `PaymentApprovedOrder`。因此,在 *PaymentApprovedOrder.cs* 文件中,`PaymentApprovedOrder` 的 `Customer` 声明如下:

public virtual Customer PaidByCustomer { get; set;}

在 *PaymentApprovedOrder.hbm* 映射文件中,它被映射为

<many-to-one name ="PaidByCustomer"  class ="Customer" column ="CUSTOMERID" 
  not-null="true" insert="false" update="false" 
  cascade ="save-update"></many-to-one>

上面的代码片段展示了 `PaymentApprovedOrder` 和 `Customer` 之间多对一关联的映射。上述映射中的 `not-null=true` 属性是指定 `PaymentApprovedOrder` 必须存在客户的约束。`<many-to-one>` 标签没有 `inverse` 属性。最有趣的部分是 `insert=false` 和 `update=false` 属性,它们替代了 `inverse` 属性的缺失。这些属性阻止 NHibernate 在使用此端的对象更改此端的链接时自动生成 Insert 和 Update 语句。通过这种映射机制,可以确保 `<list>` 无需设置 `inverse` 属性,从而获得完整的列表排序信息。如果 `<list>` 使用 `inverse=true` 属性进行映射,则列表中的元素排序信息或位置信息将设置为 null。`PaymentApprovedOrder`、`Customer` 和用于测试的客户端代码显示在图 9、图 10、图 11 中。所有其他类可在本系列文章的第 1 部分和第 2 部分中找到。可下载代码将在本系列文章的第 7 部分中提供。

图 9 - 带有映射的新 PAYMENTAPPROVEDORDER 类

图 10 - 带有映射的 CUSTOMER 类

图 11 - <list> 集合映射的 TestClientCode

请看图 12。它显示了运行图 11 所示的测试客户端代码创建的 PAYMENTAPPROVEDORDER 表的行。如果您仔细查看图 12 中表的最后一列,即 PAIDORDER_LIST_POSITION,它捕获了每个客户已支付订单列表的排序信息,您会发现对于每个客户,它都以值 0 开始,表示列表的起始点,并且每添加一个订单就会递增,然后对于下一个客户,它会重置为 0,表示为下一个客户启动一个新列表。因此,`<LIST>` 的排序信息得到了维护。在图 12 所示的 PAYMENTAPPROVEDORDER 表行中,另一个最重要的观察是,外键 CUSTOMERID 没有空值。因此,它捕捉了领域场景,即没有 CUSTOMER 就无法创建 PAYMENTAPPROVEDORDER。正确的一对多映射。

图 12 - 图 11 中测试代码的 PAYMENTAPPROVEDORDER 表行。

关注点

文章开头提到一对多关联非常重要。原因是,在 OOAD 中处理多对多关联的常见做法是使用关联类并将多对多关联分解为两个一对多关联。下一篇文章将讨论可选的一对多关联,之后我们将讨论多对多关联。尽情享受 NHibernate。

© . All rights reserved.