NHibernate学习手记(6) - 实现one2many/many2one的映射

一对多(one2many)是最常见的对象关系之一,本文将通过示例说明如何使用NH来实现one2many关系的映射,以及如何实现Parent/Child对象之间的级连操作。

根据 约定,本文将通过Category和Item对象来描述one2many的关系,即一个Category对象对应多个Item对象。
NHibernate学习手记(6) - 实现one2many/many2one的映射

主要内容:
1、编写POCO类
2、准备数据库
3、编写配置文件
4、级连(cascading)操作示例

一、编写POCO类
从手记(6)起我打算由编写POCO类开始描述,因为NHibernate已经让我们以对象的方式去思考数据操作,数据表该怎么设计已经不是思维的起点,更不是重点。

1、Category的POCO类:
 1  using  System;
 2  using  System.Collections;
 3 
 4  namespace  TestOne2Many
 5  {
 6       ///  <summary>
 7       ///  Category 的摘要说明
 8       ///  </summary>
 9       ///  创 建 人: Aero
10       ///  创建日期: 2006-3-17
11       ///  修 改 人: 
12       ///  修改日期:
13       ///  修改内容:
14       ///  版    本:
15       public  class  Category
16      {
17           private  Guid _categoryId;
18 
19           private  string  _name  =  string .Empty;
20 
21           private  IList _items;
22 
23           public  Guid CategoryID
24          {
25               get  {  return  this ._categoryId; }
26               set  {  this ._categoryId  =  value; }
27          }
28 
29           public  string  Name
30          {
31               get  {  return  this ._name; }
32               set  {  this ._name  =  value; }
33          }
34 
35           public  IList Items
36          {
37               get  {  return  this ._items; }
38               set  {  this ._items  =  value; }
39          }
40 
41           #region  构造函数
42           ///  <summary>
43           ///  默认无参构造函数
44           ///  </summary>
45           ///  创 建 人: Aero
46           ///  创建日期: 2006-3-17
47           ///  修 改 人: 
48           ///  修改日期:
49           ///  修改内容:
50           public  Category()
51          {
52               this ._items  =  new  ArrayList();
53          }
54          
55           #endregion
56      }
57  }
58 

一对多的关系在.net代码里是以集合的形式去表示的,按照NH的online document的说法,NH支持三种类型的集合:System.Collections.IList、System.Collection.IDictionary、Iesi.Collections.ISet,在进行O/R mapping时,NH将自动把上述集合转化为NHibernate.Collection中对应的集合类型。

下面的测试代码无法通过,因为NH已经把Category.Items转化为NHibernate.Collection.Bag
 1  [Test]
 2  public  void  TestCollectionType()
 3  {
 4       using  (ISession session  =  TestCategory.Factory.OpenSession())
 5      {
 6           //  We assume that there are only one category object in the repository,
 7           //  see initialization in TestCategory.TestInitialize().
 8           //  note: cascading option should set as "all-delete-orphan" in objects.hbm.xml
 9          Category expectedCategory  =  
10              session.CreateCriteria( typeof (Category)).List()[ 0 as  Category;
11 
12           //  that works?
13          Assert.AreEqual(expectedCategory.Items.GetType(),  typeof (System.Collections.ArrayList));
14      }
15  }


2、Item的POCO类:
 1  using  System;
 2 
 3  namespace  TestOne2Many
 4  {
 5       ///  <summary>
 6       ///  Item 的摘要说明
 7       ///  </summary>
 8       ///  创 建 人: Aero
 9       ///  创建日期: 2006-3-17
10       ///  修 改 人: 
11       ///  修改日期:
12       ///  修改内容:
13       ///  版    本:
14       public  class  Item
15      {    
16           private  Guid _itemId;
17 
18           private  string  _name  =  string .Empty;
19 
20           private  Category _category;
21 
22           public  Guid ItemID
23          {
24               get  {  return  this ._itemId; }
25               set  {  this ._itemId  =  value; }
26          }
27 
28           public  string  Name
29          {
30               get  {  return  this ._name; }
31               set  {  this ._name  =  value; }
32          }
33 
34           public  Category Category
35          {
36               get  {  return  this ._category; }
37               set  {  this ._category  =  value; }
38          }
39 
40           #region  构造函数
41           ///  <summary>
42           ///  默认无参构造函数
43           ///  </summary>
44           ///  创 建 人: Aero
45           ///  创建日期: 2006-3-17
46           ///  修 改 人: 
47           ///  修改日期:
48           ///  修改内容:
49           public  Item()
50          {
51               //
52               //  TODO: 在此处添加构造函数逻辑
53               //
54          }
55          
56           #endregion
57      }
58  }
59 

二、准备数据库
新建数据库nh_categories和nh_items,数据库设计如下:
NHibernate学习手记(6) - 实现one2many/many2one的映射

或直接执行以下sql语句(本文示例代码所使用的数据库名称为NHTrial)
use  NHTrial
GO

if  exists  ( select  *  from  dbo.sysobjects  where  id  =  object_id (N ' [dbo].[nh_categories_nh_items_FK1] ' and  OBJECTPROPERTY (id, N ' IsForeignKey ' =  1 )
ALTER  TABLE  [ dbo ] . [ nh_items ]  DROP  CONSTRAINT  nh_categories_nh_items_FK1
GO

if  exists  ( select  *  from  dbo.sysobjects  where  id  =  object_id (N ' [dbo].[nh_categories] ' and  OBJECTPROPERTY (id, N ' IsUserTable ' =  1 )
drop  table  [ dbo ] . [ nh_categories ]
GO

if  exists  ( select  *  from  dbo.sysobjects  where  id  =  object_id (N ' [dbo].[nh_items] ' and  OBJECTPROPERTY (id, N ' IsUserTable ' =  1 )
drop  table  [ dbo ] . [ nh_items ]
GO

CREATE  TABLE  [ dbo ] . [ nh_categories ]  (
    
[ CategoryID ]  [ uniqueidentifier ]  NOT  NULL  ,
    
[ Name ]  [ nvarchar ]  ( 50 ) COLLATE Chinese_PRC_CI_AS  NOT  NULL  
ON  [ PRIMARY ]
GO

CREATE  TABLE  [ dbo ] . [ nh_items ]  (
    
[ ItemID ]  [ uniqueidentifier ]  NOT  NULL  ,
    
[ CategoryID ]  [ uniqueidentifier ]  NOT  NULL  ,
    
[ Name ]  [ nvarchar ]  ( 50 ) COLLATE Chinese_PRC_CI_AS  NOT  NULL  
ON  [ PRIMARY ]
GO

ALTER  TABLE  [ dbo ] . [ nh_categories ]  WITH  NOCHECK  ADD  
    
CONSTRAINT  [ nh_categories_PK ]  PRIMARY  KEY    CLUSTERED  
    (
        
[ CategoryID ]
    )  
ON  [ PRIMARY ]  
GO

ALTER  TABLE  [ dbo ] . [ nh_items ]  WITH  NOCHECK  ADD  
    
CONSTRAINT  [ nh_items_PK ]  PRIMARY  KEY    CLUSTERED  
    (
        
[ ItemID ]
    )  
ON  [ PRIMARY ]  
GO

ALTER  TABLE  [ dbo ] . [ nh_items ]  ADD  
    
CONSTRAINT  [ nh_categories_nh_items_FK1 ]  FOREIGN  KEY  
    (
        
[ CategoryID ]
    ) 
REFERENCES  [ dbo ] . [ nh_categories ]  (
        
[ CategoryID ]
    )
GO

三、编写配置文件
1、新建hibernate.cfg.xml文件,设置hibernate的运行配置信息。
<? xml version="1.0" encoding="utf-8"  ?>
< hibernate-configuration   xmlns ="urn:nhibernate-configuration-2.0"  >
    
< session-factory  name ="TestOne2Many" >
        
<!--  properties  -->
        
< property  name ="connection.provider" > NHibernate.Connection.DriverConnectionProvider </ property >
        
< property  name ="connection.driver_class" > NHibernate.Driver.SqlClientDriver </ property >
        
< property  name ="connection.connection_string" > Server=localhost;database=NHTrial;User Id=sa;Password=sa </ property >
        
< property  name ="show_sql" > false </ property >
        
< property  name ="dialect" > NHibernate.Dialect.MsSql2000Dialect </ property >
        
< property  name ="use_outer_join" > true </ property >
    
</ session-factory >
    
</ hibernate-configuration >

2、新建文件objects.hbm.xml,配置Category和Item类的o/r mapping信息,并且把objectes.hbm.xml的属性设置为“嵌入资源”。
子非鱼在 NHibernate学习里说实体xxx的mapping信息要写在xxx.hbm.xml文件里面,其实nhibernate没有这个限制,完全可以把n个实体的配置信息写在一个hbm.xml文件中。
 1  <? xml version="1.0" encoding="utf-8"  ?>  
 2  < hibernate-mapping  xmlns ="urn:nhibernate-mapping-2.0" >
 3       < class  name ="TestOne2Many.Category, TestOne2Many"  table ="nh_categories" >
 4           < id  name ="CategoryID"  column ="CategoryID"  type ="Guid"  
 5              unsaved-value ="00000000-0000-0000-0000-000000000000" >
 6               < generator  class ="guid"  />
 7           </ id >
 8          
 9           < property  name ="Name"  type ="string"  length ="50"  />
10           < bag  name ="Items"  lazy ="true"  inverse ="true"  cascade = "all-delete-orphan" >
11               < key  column ="CategoryID"  />
12               < one-to-many  class ="TestOne2Many.Item, TestOne2Many"  />
13           </ bag >
14       </ class >
15      
16       < class  name ="TestOne2Many.Item, TestOne2Many"  table ="nh_items" >
17           < id  name ="ItemID"  column ="ItemID"  type ="Guid"  
18              unsaved-value ="00000000-0000-0000-0000-000000000000" >
19               < generator  class ="guid"  />
20           </ id >
21          
22           < property  name ="Name"  type ="string"  length ="50"  />
23           < many-to-one  name ="Category"  class ="TestOne2Many.Category, TestOne2Many"  
24              column ="CategoryID"  />
25       </ class >
26      
27  </ hibernate-mapping >
28 
部分配置节点的含义和用法在 NHibernate学习手记(5) - 简单的对象映射里已经说过了,这里只看看bag、one-to-many和many-to-one。

1)bag节点:用于定义System.Collection.IList的类型的集合元素。

Attributes

Usage

Example

name

指示映射的属性名称。Required

Items (Category.Items)

lazy

指示是否使用延迟加载。Optional

true | false

cascade

指示级连操作类型。Optional

all


2)级连操作选项说明:

value

usage

none

默认值,不进行级连操作。

save-update

进行级连saveupdate操作

delete

进行级连删除操作

delete-orphan

删除无相关的父对象的子对象

all

进行级连save/update/delete操作

all-delete-orphan

all + delete-orphan

当进行save-update级连操作时,NH将根据子对象主键的unsave-value来判断该执行save还是update操作。

3)key节点:用于指定nh_items表中用作外键(和nh_categories)的数据列名称
4)one-to-many节点:用于指定子对象的类型(全限定名称)
5)many-to-one节点:用于指定父对象属性,如Item.Category

Attributes

Usage

Example

name

指示映射的属性名称。Required

Category (Item.Category)

class

指示指示父对象的全限定名称。Required

TestOne2Many.Category, TestOne2Many

column

指示子表的外键列名称。Required

CategoryID


四、示例one2many的级连操作。
看过子非鱼兄的 NHibernate学习后,发现用单元测试来进行代码示例的确是一种非常有效的方式,大家只需要的是再增加一个NUnit.framework的引用:)。
1、级连添加:在保存新增的Category对象时,级连保存与之关联的Item对象
 1  ///  <summary>
 2           ///  demonstrate how to execute a the cascading save
 3           ///  </summary>
 4          [Test]
 5           public  void  TestCascadingSave()
 6          {
 7               using  (ISession session  =  TestCategory.Factory.OpenSession())
 8              {
 9                   //  prepare test objects
10                  Category expectedCategory  =  new  Category();
11                  expectedCategory.Name  =  " category "  +  System.Environment.TickCount.ToString();
12 
13                   for  ( int  i  =  0 ; i  <  10 ; i ++ )
14                  {
15                      Item item  =  new  Item();
16                      item.Name  =  " item "  +  System.Environment.TickCount.ToString();
17                      item.Category  =  expectedCategory;
18 
19                      expectedCategory.Items.Add(item);
20                  }
21 
22                   //  save objects in a all-cascading way
23                   //  note: cascading option should at least set as "all" in objects.hbm.xml
24                  ITransaction trans  =  session.BeginTransaction();
25 
26                   try
27                  {
28                      session.SaveOrUpdate(expectedCategory);
29                      trans.Commit();
30                  }
31                   catch
32                  {
33                      trans.Rollback();
34                       throw ;
35                  }
36 
37                   //  that works?
38                  Category actualCategory  =  
39                      session.Get( typeof (Category), expectedCategory.CategoryID)  as  Category;
40                  Assert.IsNotNull(actualCategory);
41                  Assert.AreEqual(expectedCategory.Items.Count, actualCategory.Items.Count);
42              }
43          }

2、级连更新(update):移除Category对象的部分Item子对象,保存更改后,被移除的Item子对象从数据库中删除。
 1  ///  <summary>
 2           ///  demonstrate how to remove sub-items and execute a cascading update
 3           ///  </summary>
 4          [Test]
 5           public  void  TestCascadingUpdate()
 6          {
 7               using  (ISession session  =  TestCategory.Factory.OpenSession())
 8              {
 9                   //  We assume that there are only one category object in the repository,
10                   //  see initialization in TestCategory.TestInitialize().
11                   //  note: cascading option should set as "all-delete-orphan" in objects.hbm.xml
12                  Category expectedCategory  =  
13                      session.CreateCriteria( typeof (Category)).List()[ 0 as  Category;
14                   int  expectedItemCount  =  expectedCategory.Items.Count;
15 
16                   //  execute a cascading update
17                  ITransaction trans  =  session.BeginTransaction();
18 
19                   try
20                  {
21                       //  remove an item from item-collection from the repository
22                      expectedCategory.Items.RemoveAt( 0 );
23 
24                      session.Update(expectedCategory);
25                      trans.Commit();
26                  }
27                   catch  (System.Exception e)
28                  {
29                      trans.Rollback();
30                       throw ;
31                  }
32 
33                   //  that works?
34                  Assert.AreEqual( 1 , session.CreateCriteria( typeof (Category)).List().Count);
35                  Assert.AreEqual(expectedItemCount  -  1 , session.CreateCriteria( typeof (Item)).List().Count);
36              }
37          }

3、级连删除:当父Category对象被删除,与之关联的Item对象也被删除。
 1  ///  <summary>
 2           ///  demonstrate how to execute a cascading deletion
 3           ///  </summary>
 4          [Test]
 5           public  void  TestCascadingDelete()
 6          {
 7               using  (ISession session  =  TestCategory.Factory.OpenSession())
 8              {
 9                   //  We assume that there are only one category object in the repository,
10                   //  see initialization in TestCategory.TestInitialize().
11                   //  note: cascading option should set as "all-delete-orphan" in objects.hbm.xml
12                  Category expectedCategory  =  
13                      session.CreateCriteria( typeof (Category)).List()[ 0 as  Category;
14 
15                   //  remove category from the repository
16                  ITransaction trans  =  session.BeginTransaction();
17 
18                   try
19                  {
20                      session.Delete(expectedCategory);
21                      trans.Commit();
22                  }
23                   catch  (System.Exception e)
24                  {
25                      trans.Rollback();
26                       throw ;
27                  }
28 
29                   //  that works?
30                  Assert.AreEqual( 0 , session.CreateCriteria( typeof (Category)).List().Count);
31                  Assert.AreEqual( 0 , session.CreateCriteria( typeof (Item)).List().Count);
32              }
33          }

要特殊指出的是,NH不支持通过Category.Item=null这种方式来删除与Category对象关联的Item对象。
 1  [Test, ExpectedException( typeof (NHibernate.HibernateException))]
 2           public  void  TestCascadingUpdateFail()
 3          {
 4               using  (ISession session  =  TestCategory.Factory.OpenSession())
 5              {
 6                   //  We assume that there are only one category object in the repository,
 7                   //  see initialization in TestCategory.TestInitialize().
 8                   //  note: cascading option should set as "all-delete-orphan" in objects.hbm.xml
 9                  Category expectedCategory  =  
10                      session.CreateCriteria( typeof (Category)).List()[ 0 as  Category;
11                   int  expectedItemCount  =  expectedCategory.Items.Count;
12 
13                   //  execute a cascading update
14                  ITransaction trans  =  session.BeginTransaction();
15 
16                   try
17                  {
18                       //  we can't remove all items by dereference Category.Items as null,
19                       //  this will cause a NHiberate.HibernateException
20                      expectedCategory.Items  =  null ;
21 
22                       //  still we can't remove items in the following way,
23                       //  this will cause a NullReference Exception from NHibernate
24                      
25                       // expectedCategory.Items[0] = null;
26 
27                      session.Update(expectedCategory);
28                      trans.Commit();
29                  }
30                   catch  (System.Exception e)
31                  {
32                      trans.Rollback();
33                       throw ;
34                  }
35 
36                   //  that works?
37                  Assert.AreEqual( 1 , session.CreateCriteria( typeof (Category)).List().Count);
38                  Assert.AreEqual(expectedItemCount, session.CreateCriteria( typeof (Item)).List().Count);
39              }
40          }

完整示例代码可从 ObjectMappings.rar下载,其中的TestOne2Many即本文所讨论的工程。

你可能感兴趣的:(Hibernate)