第一章第三节的翻译见Creating class hierarchy mappings
http://www.cnblogs.com/szp1118/archive/2010/12/17/1908965.html
映射一对多的关联关系
一个实体关联另一个实体那是很常见的,也是我们应用程序必不可少的。在这个例子中,我将向你展示怎么样去映射一个一对多(one-to-many)的关联关系,通过之前的Movies实体以及新的实体ActorRoles去进行映射。
准备工作
完成上一节的创建一个继承关系的映射的例子,这一节的工作将基于上一节的工作之上开始的。
如何去做呢.....
创建一个新的类,并命名为ActorRole,代码如下所示:
{
public class ActorRole : Entity
{
public virtual string Actor { get ; set ; }
public virtual string Role { get ; set ; }
}
}
为ActorRole类创建一个嵌入式的资源文件,ActorRole.hbm.xml,代码如下:
< 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属性,这就是一个一对多的关联关系:
namespace Eg.Core
{
public class Movie : Product
{
public virtual string Director { get ; set ; }
public virtual IList<ActorRole> Actors { get; set ; }
}
}
为Movie.hbm.xml映射文件增加list元素节点,代码如下:
< 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, Actor和 Role字段,分别对应类中的属性,而MovieId和ActorIndex两个字段分别来自于Movie映射文件中的list元素节点,该元素节点定义了Actors集合类型的属性,注意,这两个字段在ActorRole映射文件中是没有任何定义的。
在Movie类的Actors属性中,我们使用了一个IList的集合类型,无论是使用NHibernate还是说从一个好的编程实践来说,使用接口编程我们是强烈推荐的,而且对于NHibernate来说,使用一个接口来定义属性的类型就可以允许NHibernate实现自己的集合类型,使之支持延迟加载(lazy loading),这个将会在后面讨论。
在我们的Movie映射文件中,属性Actors被映射到了一个list的元素节点,为了让Movie和ActorRole在数据库中建立一对多的关联关系,我们在ActorRole表中使用了外键字段MovieId进行关联,每个ActorRole的Movie's Id值都将会被持久化到该字段中,在list元素节点中的key元素节点告诉NHibernate持久化ActorRole对象时,把它的Movie's Id存放到MovieId字段。
我们已经定义了Actors属性的类型是IList,这就决定了它里面的ActorRole对象是有先后顺序的,第一个ActorRole对象在名单中也应该是排名最前面的,所以在list元素节点中的index元素节点定义了ActorIndex字段来存储集合中的元素的索引序号。同时,<one-to-many class="ActorRole" />了告诉NHibernate,Actors集合中存放的是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对象:
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表的数据。
Console.WriteLine(actor.Actor);
当我们第一次去枚举这个集合的时候,之前的延迟加载对象被初始化了,它会从数据库中加载和Movie对象实例相关的ActorRole数据,使用的查询语句见下面所示:
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 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接口,bag和set还支持双向的关联关系。
Bags
一个包(bag)集合允许里面的元素重复,但是并不能进行排序,元素的顺序是不定的。让我们来讨论一下ActorRole实体集合(即Movie类的Actors属性,现在改用bag来实现),这个bag集合或许包含actor role 1,actor role 2, actor role 3, actor role 1, actor role 4, 和 actor role 1.映射文件中典型的配置如下:
< 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,映射文件中看起来如下:
< 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映射看起来如下:
< 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映射看起来如下:
< key column ="MovieId" />
< one-to-many class ="ActorRole" />
</ set >
和set对应的Actors属性类型应该是ISet,它来自Iesi.Collections. dll程序集。到目前为止,NHibernate没有直接支持包含在.NET Framework 4中的ISet接口。如果尝试向一个未初始化的延迟加载的set集合增加一个元素,则会引起该集合从数据库加载数据,因为set不允许元素重复,所以这显然是必要的,加载数据用于判断是否元素重复。为了确保set中的元素的唯一性,你需要重写基类中的Equals和GetHashCode方法,这会在以后详细讨论。
Map
Map是NHibernate从java引入的另一个术语,在.net中,它就是字典(dictionary),集合中的元素是由键值对组成的,键必须是唯一的,值可以不唯一。
< key column ="MovieId" />
< map-key column ="Role" type ="string" />
< element column ="Actor" type ="string" />
</ map >
你可能已经猜到了,Actors属性的类型必须被定义成IDictionary<string, string>,键是电影角色的名称,而值是actor的名字。就像下面的代码展示的那样,键和值你不一定使用基本类型,NHibernate同样允许使用实体作为键和值
< key column ="Id" />
< index-many-to-many class ="KeyEntity" />
< many-to-many class ="ValueEntity" />
</ map >
最后是随书的源代码