NHibernate.3.0.Cookbook第一章第四节Mapping a one-to-many relationship的翻译

第一章第三节的翻译见Creating class hierarchy mappings 

http://www.cnblogs.com/szp1118/archive/2010/12/17/1908965.html 

 

映射一对多的关联关系

 

一个实体关联另一个实体那是很常见的,也是我们应用程序必不可少的。在这个例子中,我将向你展示怎么样去映射一个一对多(one-to-many)的关联关系,通过之前的Movies实体以及新的实体ActorRoles去进行映射。

 

准备工作

完成上一节的创建一个继承关系的映射的例子,这一节的工作将基于上一节的工作之上开始的。

 

如何去做呢.....

创建一个新的类,并命名为ActorRole,代码如下所示:

namespace Eg.Core
{
public class ActorRole : Entity
{
public virtual string Actor { getset; }
public virtual string Role { getset; }
}
}

 

 ActorRole创建一个嵌入式的资源文件,ActorRole.hbm.xml,代码如下:

ActorRole.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly
="Eg.Core"
namespace
="Eg.Core">
<class name="ActorRole">
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="Actor" not-null="true" />
<property name="Role" not-null="true" />
</class>
</hibernate-mapping>

 

Movie类中增加Actors属性,这就是一个一对多的关联关系

 

using System.Collections.Generic;
namespace Eg.Core
{
public class Movie : Product
{
public virtual string Director { getset; }
public virtual IList<ActorRole> Actors { getset; }
}
}

 

Movie.hbm.xml映射文件增加list元素节点,代码如下:

 

<subclass name="Movie" extends="Product">
<property name="Director" />
<list name="Actors" cascade="all-delete-orphan">
<key column="MovieId" />
<index column="ActorIndex" />
<one-to-many class="ActorRole"/>
</list>
</subclass>

 

 

 它是如何工作的呢

我们的ActorRole实体类的映射是非常简单的,就是单个类的映射,结合映射文件仔细看一下它的代码就会发现,它不是Product类层次结构的一部分,即ActorRole类没有继承Product类。所以在数据库中,ActorRole有自己的独立的表,如下所示:

  就如我们预想的那样,ActorRole表带有Id, ActorRole字段,分别对应类中的属性,而MovieIdActorIndex两个字段分别来自于Movie映射文件中的list元素节点,该元素节点定义了Actors集合类型的属性,注意,这两个字段在ActorRole映射文件中是没有任何定义的。

  在Movie类的Actors属性中,我们使用了一个IList的集合类型,无论是使用NHibernate还是说从一个好的编程实践来说,使用接口编程我们是强烈推荐的,而且对于NHibernate来说,使用一个接口来定义属性的类型就可以允许NHibernate实现自己的集合类型,使之支持延迟加载(lazy loading),这个将会在后面讨论。

  在我们的Movie映射文件中,属性Actors被映射到了一个list的元素节点,为了让Movie和ActorRole在数据库中建立一对多的关联关系,我们在ActorRole表中使用了外键字段MovieId进行关联,每个ActorRoleMovie's Id值都将会被持久化到该字段中,在list元素节点中的key元素节点告诉NHibernate持久化ActorRole对象时,把它的Movie's Id存放到MovieId字段。

  我们已经定义了Actors属性的类型是IList,这就决定了它里面的ActorRole对象是有先后顺序的,第一个ActorRole对象在名单中也应该是排名最前面的,所以在list元素节点中的index元素节点定义了ActorIndex字段来存储集合中的元素的索引序号。同时,<one-to-many class="ActorRole" />告诉NHibernateActors集合中存放的是ActorRoles类型的对象。

   而属性cascade我们把它设为all-delete-orphan,这就告诉NHibernate,在保存一个Movie实例时需要同时保存和它关联的所有ActorRole对象实例,并且在删除一个Movie实例时也同时需要删除所有的ActorRole对象实例。

 

更多内容

这一节中还有其它相关内容需要讨论

 

延迟加载集合数据

  为了提高应用程序的性能,NHibernate支持延迟加载(lazy loading)有时也称为惰性加载,简而言之就是数据库中的数据不是马上就被加载到内存中的,而是直到应用程序真正需要这些数据的时候才被加载进来的。让我们来看一下应用程序从数据库中获取一个movie实例的具体过程:

1. 当需要通过一个给定的Id实例化一个Movie 对象时(即通过主键值加载单个Movie对象),NHibernate首先从数据库中取得Id, Name, Description, UnitPrice, Director字段的数据,这里需要注意的是我们并没有去加载Actors属性的数据,NHibernate会自动生成如下的查询语句去实例化一个Movie对象:

select
movie0_.Id 
as Id1_,
movie0_.Name 
as Name1_,
movie0_.Description 
as Descript4_1_,
movie0_.UnitPrice 
as UnitPrice1_,
movie0_.Director 
as Director1_
from Product movie0_
where
movie0_.ProductType
='Eg.Core.Movie' and
movie0_.Id 
= 'a2c42861-9ff0-4546-85c1-9db700d6175e'

 

2. NHibernate创建一个Movie类型的实例对象。

3. NHibernate将刚才从数据库中获取的字段的值赋值到对应的Id, Name, Description, UnitPrice, Director属性。

4. NHibernate创建了一个指定的用于延迟加载的对象,该对象的类型实现了IList<ActorRole>接口,这个对象被赋值给了属性Actors,注意,这个对象不是List<ActorRoles>的实例,这个对象的类型是独立的,是由NHibernate实现了IList<ActorRole>接口而创建的。

5.N Hibernate返回Movie的实例对象给我们的应用程序。

 

接下来,假定我们的应用程序包含了下列的代码,记住,在这个代码之前我们还没有加载任何的ActorRole数据,没有从数据库中取得ActorRole表的数据。

foreach (var actor in movie.Actors)
Console.WriteLine(actor.Actor);

 

当我们第一次去枚举这个集合的时候,之前的延迟加载对象被初始化了,它会从数据库中加载和Movie对象实例相关的ActorRole数据,使用的查询语句见下面所示:

SELECT
actors0_.MovieId 
as MovieId1_,
actors0_.Id 
as Id1_,
actors0_.ActorIndex 
as ActorIndex1_,
actors0_.Id 
as Id0_0_,
actors0_.Actor 
as Actor0_0_,
actors0_.Role 
as Role0_0_
FROM ActorRole actors0_
WHERE
actors0_.MovieId
= 'a2c42861-9ff0-4546-85c1-9db700d6175e'

 我们也可以为Movie.hbm.xml映射文件的list元素节点加上lazy="false",这样延迟加载就被禁用了。在我们获取Movie实例对象时它的Actors属性就同时被初始化了。

 

延迟加载代理

  在其它的地方,NHibernate同样也可以通过使用代理对象支持延迟加载,假如我们的ActorRole类有一个对Movie引用,这样我们就可以通过ActorRole实例直接导航到Movie实例,代码如下所示:

public class ActorRole : Entity
{
public virtual string Actor { get; set; }
public virtual string Role { get; set; }
public virtual Movie Movie { get; set; }
}

 
  如果我们从数据库中取得一个ActorRole对象,NHibernate为我们生成ActorRole对象,但是我们可以预见的是,如果NHibernate只从ActorRole表取得数据的话,它只能得到Movie属性对象的Id属性,除非要从两张表里取数据。但是从两张表里取数据往往是不必要的,如果我们实例化一个ActorRole对象只是用它自己本身属性,而不需要去取和它关联的Movie对象的话,从两张表取数据会对性能造成一定的影响。取而代之的是,NHibernate会创建一个代理类的对象赋值给Movie属性,使之能够启用延迟加载。

   当然,我们在没有加载movie的情况下,照样可以访问它的Id的值(该值存放在ActorRole表中,无需关联到Movie表获取),如果我们需要访问其他的属性或者方法,那么NHibernate就会立即到数据库中去加载所有的movie字段数据,加载这些数据对应用程序来说是完全透明的。这个代理对象和一个真正的Movie对象非常相似的。

  这个代理对象其实是Movie对象的一个子类,为了让子类能够继承Movie类,而且为了支持延迟加载,子类需要拦截这些调用,NHibernate要求我们的实体类必须遵循下面的设计要求:

 1.Movie不能够是一个sealed的类,如果类是sealed的,那么就无法让子类继承了。

 2.Movie类必须有一个访问级别是保护或者公共的不带任何参数的构造函数。代理类继承该类,并且会调用基类的无参构造函数。

 3.所有的公共成员必须是带有virtual关键字,包括方法。如果无virtual关键字,则在使用多态的情况下会调用类型本身的方法,而不是具体类的方法。

 

  NHibernate给了我们多种选择去创建这些代理对象,一般是使用DynamicProxy框架,它是Castle项目的一部分,NHibernate也支持使用LinFu以及Spring.NET,而且你也可以自己实现。

如果你在Movie映射文件中的class元素节点中明确指定lazy="false”,那么我们将禁用这个行为。NHibernate将不会为Movie创建任何代理对象,这将迫使NHibernate在任何时间加载一个ActorRole对象时立即就加载和它关联的movie的数据,像这样去加载不必要的数据会严重影响你的应用程序的性能,你应该仅仅在具体指定的,并且是仔细考虑过的情况下去使用,即不是很有必要就不要使用。

 

集合

NHibernate支持很多集合类型,最常用的类型如下:

 

 

Bag

Set

List

Map

允许元素重复

键不允许重复
值允许重复

是否支持排序

 

类型

IList

Iesi.Collections.ISet

IList

IDictionary

所有的集合都实现了ICollection接口,而一个自定义的集合类型还实现了NHibernate.UserType.IUserCollection接口,bagset还支持双向的关联关系。

 

Bags

  一个包(bag)集合允许里面的元素重复,但是并不能进行排序,元素的顺序是不定的。让我们来讨论一下ActorRole实体集合(Movie类的Actors属性,现在改用bag来实现),这个bag集合或许包含actor role 1actor role 2, actor role 3, actor role 1, actor role 4, actor role 1.映射文件中典型的配置如下:

<bag name="Actors">
<key column="MovieId"/>
<one-to-many class="ActorRole"/>
</bag>

 

相应地Actors属性的类型可以是IList 或者 ICollection,,甚至也可以是IEnumerable,通过简单的SQL语句,包内的相同元素是没有办法进行区分的,例如,无法构建一个SQL语句来删除包内的第二个actor role 1元素,这个SQL语句delete from Actors where ActorRoleId='1'将会删除所有三个actor role 1元素。在包中,当有一个元素被移除时,则整个被更新过的包集合都会被持久化,也就是说,在数据库中首先全部删除这些元素,然后再重新插入,对于很大的包集合来说,这样做会带来性能上的问题。

(注:虽然Actors属性类型可以试IList,但是其索引号并不存入数据库,所以无法进行区分和排序,从数据库重新取出来之后的顺序是不定的,这就是Bag和前面的List的区别)

 

为了解决这个问题(即更新元素需要全部删除再重新插入的问题),NHibernate同时提供了一个idBag,在这个包中的每一个元素都会被分配一个ID,这个ID是由指定的POID生成器生成的,这样就允许NHibernate 能够唯一地区分和处理每一个包内的元素,比如类似这样的查询delete from Actors where ActorRoleBagId='2'

对于一个idBag,映射文件中看起来如下:

<idBag name="Actors">
<collection-id column="ActorRoleBagId" type="Int64">
<generator class="hilo" />
</collection-id>
<key column="MovieId"/>
<one-to-many class="ActorRole"/>
</idBag>

 

 Lists

  一个list集合同样也支持元素重复,但是它不像刚才的包(bag),list支持元素排序。我们的list或许在索引0处是actor role 1,索引1处是actor role 2,索引2处是actor role 3,索引3处是actor role 1索引4处是actor role 4,索引5处是actor role 1。一个典型的list映射看起来如下:

<list name="Actors">
<key column="MovieId" />
<list-index column="ActorRoleIndex" />
<one-to-many class="ActorRole"/>
</list>

 


和它相一致的Actors属性应该是一个IList类型的,因为NHibernate需要通过ActorRoleIndex字段进行排序,而该字段的值也和索引顺序相一致,同时也能够区分集合中相同的元素。然而,由于它维护者这个顺序,所以一旦我们的list中的元素有所改变,它们的索引顺序也将被重置。例如,假设我们有一个6个actor roles的list集合,现在我们移除了第三个actor role,那么NHibernate将会更新集合中每一个元素的ActorRoleIndex值。

 

Sets

  一个set集合不允许元素重复,而且也不保存元素顺序,在我们的应用程序中,这是最常见的一种类型,一个set集合比如包含actor role 1, actor role 3, actor role 2, actor role 4四个元素,假如尝试新增一个actor role 1元素到该集合中,则会新增失败。一个典型的set映射看起来如下:

<set name="Actors">
<key column="MovieId" />
<one-to-many class="ActorRole"/>
</set>

 


set对应的Actors属性类型应该是ISet,它来自Iesi.Collections. dll程序集。到目前为止,NHibernate没有直接支持包含在.NET Framework 4中的ISet接口。如果尝试向一个未初始化的延迟加载的set集合增加一个元素,则会引起该集合从数据库加载数据,因为set不允许元素重复,所以这显然是必要的,加载数据用于判断是否元素重复。为了确保set中的元素的唯一性,你需要重写基类中的EqualsGetHashCode方法,这会在以后详细讨论。

 

Map

  MapNHibernatejava引入的另一个术语,在.net中,它就是字典(dictionary),集合中的元素是由键值对组成的,键必须是唯一的,值可以不唯一。

<map name="Actors" >
<key column="MovieId" />
<map-key column="Role" type="string" />
<element column="Actor" type="string"/>
</map>

 


你可能已经猜到了,Actors属性的类型必须被定义成IDictionary<string, string>,键是电影角色的名称,而值是actor的名字。就像下面的代码展示的那样,键和值你不一定使用基本类型,NHibernate同样允许使用实体作为键和值

<map name="SomeProperty">
<key column="Id" />
<index-many-to-many class="KeyEntity"/>
<many-to-many class="ValueEntity" />
</map>

 

最后是随书的源代码

 /Files/szp1118/Eg.Core.rar

posted @ 2010-12-31 15:42  喆_喆  阅读(1337)  评论(0编辑  收藏  举报