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

使用 NHibernate 进行对象关系映射 (ORM) - 第 2 部分(共 8 部分)编码值类型集合

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (7投票s)

2012 年 10 月 1 日

CPOL

13分钟阅读

viewsIcon

45262

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

文章系列

引言

在讨论了“一对一”实体关联的第一部分之后,接下来的进展应该是“多对一”映射。多对一映射需要了解集合(用于“多”端),因此介绍映射集合至关重要。因此,本文档通过示例描述了值类型集合的映射。首先,展示了一个与电子商务场景无关的字符串集合示例,这使得理解集合映射的概念非常简单。然后,将其应用于电子商务场景,以包含对值类型集合进行映射的某些改进。我们介绍的集合是 <SET><LIST>。NHibernate 有大量的集合可以映射,读者稍后可以通过这里列出的官方资源进行查看;但最常用的可能是 <LIST>,它也涵盖了大多数集合的映射思想。

背景

资源

此官方 NHibernate 参考下载包含 NHibernate 的完整详细信息。它相当详尽,可用于广泛参考。链接是:http://nhforge.org/doc/nh/en/index.html

描述

在本系列文章的第一部分中,我们提到值类型没有自己的表,而是存在于其所依赖的实体的表中,通过在实体表中为值类型的每个字段分配一个列来表示(还记得第一部分中 Customer 和 Email 的示例吗)。那么,如果存在一个值类型集合,如何进行映射呢?集合无法在每行的单列中容纳,例如逗号分隔的值(我们将在本系列文章的第三部分,处理实体类型集合时,详细介绍集合的对象关系映射的内部机制)。NHibernate 通过将值类型集合映射到名为“COLLECTION TABLE”的数据库表中来解决此问题。本文档展示了为 <set><list> 创建的两个这样的集合表,并强调了它们之间的区别。

Set 是一种没有重复且没有排序信息的集合。List 是一种严格保留顺序的集合。因此,在为它们创建集合表时,NHibernate 必须能够捕捉到 <list> 中如何保留排序信息,而 <set> 则不然。第一个示例(与电子商务场景无关)展示了一个 Person 类,其中包含一个喜欢的作者集合和一个喜欢的画家集合。喜欢的作者集合将使用 <set> 捕获,而喜欢的画家集合将使用 <list> 捕获。图 1 显示了 Person 类的 C# 代码(图的右上角)、person 表以及作者和画家的集合表(图的左上角),以及 Person.hbm 映射文件(图的末尾)。现在我们来详细介绍图和各种映射。

图 1 - 注意:为使图表清晰,我已从 <set name="AuthorsSet"> 中的 <element column="AUTHORNAME"...> 移除了“not-null=true”属性。稍后在“集合表的结构”部分,我将对此属性在 <set> 中的作用进行更详细的解释。

图 1 右上角的 Person 类的 C# 代码声明了一个名为 AuthorsSet 的作者姓名 SET 集合。请看图并跟随蓝色箭头。您可以看到从 C# 代码到 Person.hbm 映射文件的 SET 集合映射。

Person.cs 文件中,作者姓名 SET 声明如下:

public virtual Iesi.Collections.Generic.ISet<string> AuthorsSet { get; set; } 

请注意,使用的是 Iesi.Collections.Generic.ISet<> 而不是 System.Collections.Generic。Iesi Collections DLL 与 NHibernate 库本身一起提供下载,并且需要在项目中引用它。另外,请**注意**,集合在类中通过接口声明,并在构造函数中使用具体类型进行初始化。这是**必需的**。

Person.hbm 映射文件中,作者姓名 SET 的映射如下:

<set name ="AuthorsSet" table ="AUTHORSSET">
   <key column="PersonId"></key>
   <element column ="AUTHORNAME" type="string" not-null="true"/>
</set>

因此,Person.hbm 映射文件向 NHibernate 指示,引用 AuthorSet 是一个 VALUETYPE SET,因为它使用 table="AUTHORSET" 属性来命名集合表,并且由于 <SET> 映射中的 <element> 标签。此外,它还向 NHibernate 指示此集合的元素是 string 类型,并且集合表中的元素的列名由 column="AUTHORNAME" 指定。(注意:请记住,对于多对一或多对多实体关联,其中使用实体集合,<element> 标签不会用在 <set><list> 等内部)。一旦 NHibernate 在映射文件中看到这一点,它就会为该 SET 创建一个集合表。请看图 1 中的橙色箭头,它显示了为 Person.hbm 映射文件中声明的 SET "AuthorSet" 创建的集合表 "AUTHORSET",其中列名设置为 .hbm 文件中提到的 "AUTHORNAME"。图中显示的集合表已填充了用于测试的值。

C# 类 Person 还为画家列表声明了一个 System.Collections.Generic IList<string> PaintersList。请看图 1 并跟随紫色箭头。您可以看到从 C# 到 Person.hbm 文件的 List 集合映射。

Person.cs 文件中,画家姓名 LIST 声明如下:

public virtual IList<string> PaintersList { get; set; }

Person.hbm 文件中,画家姓名 LIST 的映射如下:

<list name="PaintersList" table="PAINTERSLIST">
   <key column ="PersonId"></key>
   <list-index column ="PAINTERSLISTINDEX"></list-index>
   <element column="PAINTERNAME" type ="string" />
</list>

与之前的作者姓名 SET 情况类似,Person.hbm 映射文件通过使用 table="PAINTERSLIST" 属性命名集合表,以及由于 <LIST> 映射中的 <element> 标签,向 NHibernate 表明引用 PaintersList 是一个 Valutype list。此外,它还向 NHibernate 指示此集合的元素是“string”类型,并且集合表中的元素列名由 column="PAINTERNAME" 指定。一旦 NHibernate 在映射文件中看到这一点,它就会为 List 创建一个集合表。请看图 1 中的绿色箭头,它显示了为 Person.hbm 映射文件中声明的 List PaintersList 创建的集合表 "PAINTERSLIST"。该集合表已填充了用于测试的值。

图 2 中下方显示了为 <SET><LIST> 创建的集合表。**请注意**,NHibernate 为 <SET><LIST> 生成集合表的方式存在差异。Set 没有顺序信息。因此,Set 只需要保留 AUTHORNAME 列中元素的信息以及它属于哪个人的 PersonId 列的信息(见图 2 - 左侧 - AUTHORSSET 表)。List 包含一个名为 PAINTERSLISTINDEX 的附加列(见图 2 - 右侧 - PAINTERSLIST 表),并且它在 Person.hbm 映射文件中指定。List 需要此列,因为 <list> 保留顺序信息,并且可以使用从零开始的索引根据此顺序信息从列表中检索项。

图 2

值是根据图 3 中显示的测试代码填充的。请看图 3 下方代码中为两个人(person 1 和 person 2)添加值的顺序,并与上面图 2 中的表值进行比较。特别是检查 PAINTERSLISTINDEX 的值。当为 person 1 添加画家时,PAINTERSLISTINDEX 的值从 0 开始,并且每次添加到 person 1 的画家列表中的元素都会递增。当向 person 2 添加画家时,PAINTERSLISTINDEX 列将重新初始化为 0,表示这是 person 2 的第一个元素,并且每次添加到画家列表中的元素都会加 1。因此,List 的 ORDER 信息得以保留。这非常重要,因为客户端代码完全有可能依赖于集合元素的顺序或使用索引快速访问集合中的元素,在这种情况下,它应该使用 <LIST> 而不是 <SET>

图 3

图 4 显示了添加到 Repository 的 C# 代码。我们将在本系列文章的第 8 部分,当讨论延迟初始化/获取时,研究处理 Repository 方法的更好解决方案。目前,要了解以这种方式处理 Repository 功能的问题,请参阅本系列文章的第一部分。我们将在本文的第 8 部分看到解决此问题的更好方法。

图 4

集合表的结构

集合表的结构包含一些有趣的细节,这些细节会影响使用 NHibernate 编码时要做的设计决策。图 5 显示了先前完成的示例中为 <SET><LIST> 生成的集合表的结构。

图 5

值得注意的是,为 <list> 集合生成的 PAINTERSLIST 集合表有一个 PRIMARYKEY,它是 PersonId 和 PAINTERSLISTINDEX(列表的索引)的组合。稍后在解释多对一关联时,我们将看到,在处理具有顺序信息的集合(如此处所示的 <list>)时,由于 <LIST-INDEX> 元素在保留顺序信息和作为复合键的一部分起着至关重要的作用,因此映射文件必须格外小心地编写,否则顺序可能会丢失并可能发生错误。另请注意,在 <SET> 集合表(即 AUTHORSSET 表)中,所有列都被设置为主键,以避免重复,从而符合 set 的定义。对于 set 集合,这是强制性的。另外请注意,如图 1 所示,为 set 添加的元素必须为 <element> 标签设置 not-null=true 属性。为什么?因为在 set 中,我们只希望添加唯一的元素,因此我们使该元素成为主键的一部分。但只有当 <element> 标签添加的项设置了“not-null=true”属性时,它们才能成为主键(显然,根据数据库基本原理,众所周知,候选键必须不为空且对每一行都不同,才能成为主键)。因此,在 <set> 的情况下,必须指定 not-null=true,如下面的代码片段所示:

<set name ="AuthorsSet" table ="AUTHORSSET">
    <key column="PersonId"></key>
    <element column ="AUTHORNAME" type="string" not-null="true"/>
</set>

在图 1 中,如果您看到图底部的映射文件,我将删除了 <set>not-null = true 属性,以确保箭头的绘制清晰。但它是有必要的,因为我们希望添加到 set 中的元素是唯一的。因此,在 <set> 的集合表示例中,您将拥有来自实体的外键和 <element> set 中的项一起作为复合键。请参考图 3,在 AUTHORSSET 表中,键显示得很清楚。

使用代码

继续电子商务示例

既然已经建立了集合的概念,下一步就是在电子商务场景中实现它。“对于网站上的每个产品描述,客户都可以输入产品的评论、产品的评分以及有用的评论。” 在不深入对象面向分析细节的情况下,我们可以安全地得出此场景有两个类:ProductDescriptionProductReview。虽然 ProductDescription 显然是一个实体,因为它具有独立的存在周期,并且将被适合同一 ProductDescription 的不同项目共享,但 ProductReview 绝对是一个值类型,因为它总是依赖于 ProductDescription。如果因为公司政策停产而从网站上删除了产品描述,那么产品评论还有什么业务价值?一个产品的评论不能被其他产品共享。因此,当一个产品停产时,它的评论也将需要被删除。所以现在可以确定 Product Review 是值类型。Product Review 也绝对是一个值类型集合,因为可能不止一个客户会留下评论。我们知道 NHibernate 中的值类型集合存储在一个名为集合表的独立表中,这非常方便,因为我们不希望 ProductReview 列使 ProductDescription 表非规范化。之前,我们映射的是 string 集合。因此,我们使用 type=string 的 <element> 标签来表示集合的项是 string 类型。但现在我们需要映射 ProductReview 类,它是一个具有自己属性的类型,如 UserCommentUserEmailIdProductRating。NHibernate 有一个 <composite-element> 标签用于将值类型类映射为集合的元素,并且它的所有属性都作为 <property> 包含在 <composite-element> 标签内。所以,在我们的例子中,如果 ProductReview.cs 定义如下:

ProductReview.cs

public class ProductReview
{
     public ProductReview() { }
 
 
     public virtual string UserComment { get; set; }
 
     public virtual string UserEmailId { get; set; }
 
     public virtual double? ProductRating { get; set; }
}

在 ProductDescription.hbm 中,其映射定义如下:

<list table="PRODUCTREVIEWS" name="ProductUserReviews">
       <key column="PRODUCTDESCRIPTIONID" not-null="true"></key>
       <list-index column="REVIEWLIST_POSITON"></list-index>
       <composite-element class="ProductReview">
         <property name="UserComment" type="string" column="USERCOMMENT"/>
         <property name="UserEmailId" type="string" column="USEREMAILID"/>
         <property name="ProductRating" type="double" column="USERRATING"/>
      </composite-element>  
</list>

在上面的代码片段中,由于 ProductReview 是一个值类型集合,因此在 ProductDescription.hbm 中将其声明为 <list> 映射(ProductDescription 是值类型 ProductReview 所依赖的实体类型。因此,ProductReviewProductDescription 的映射文件中即 ProductDescription.hbm 中进行映射)。由于它是值类型集合,因此其集合表也定义为 list 的属性,通过指定 table="PRODUCTREVIEWS" table。然后,ProductReview 类的所有属性都包含在 <composite-element> 标签内。现在看看下面的图 6,它显示了 ProductDescription.hbm 文件中 ProductReview 类的映射。看图 6 中的橙色箭头。它显示了 ProductDescription 类中的 ProductReview 集合的 C# 声明,即 IList<ProductReview>,在映射文件 ProductDescription.hbm 中,位于图底部,它相应地由橙色箭头另一端的 <list> 标签定义。接下来看绿色箭头,箭头顶端显示了 C# 类中 ProductReview 的定义及其属性,箭头底端则定义了 ProductDescription.hbm 映射文件中 <composite-element> 包装的 ProductReview 的属性。可以看到,顶端的 ProductReview C# 类的所有属性都在下面的映射文件中作为 <composite-element> 标签内的 <property> 定义。这在代码片段中也有显示。

图 6

图 7 显示了 ProductReview 值类型集合表的结构。由于 ProductReview 集合是 <LIST> 类型的值类型集合,因此定义了 REVIEWLIST_POSITION 如图 7 所示,以保留列表中元素的顺序信息。

图 7 - PRODUCTREVIEW 值类型集合表

图 8 中显示了填充了测试数据的 ProductReview 表以及客户端测试代码。

图 8 - 为图所示客户端代码生成的 PRODUCTREVIEWTABLE

<LIST> 不仅是最常用的集合类型,而且也为您可以探索的其他集合类型奠定了基础。

关注点

集合的映射方式为下一步处理多对一实体映射提供了思路,尽管这与此处讨论的值类型集合完全不同。

在本文 8 部分的第 3 部分中,将继续为多对一映射奠定基础。

© . All rights reserved.