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

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

根据约定,本文将通过Category和Item对象来描述one2many的关系,即一个Category对象对应多个Item对象。


主要内容:
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()[0as 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,数据库设计如下:


或直接执行以下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()[0as 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()[0as 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()[0as 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即本文所讨论的工程。
posted @ 2008-08-21 23:39  永不放弃-Jack wu  阅读(362)  评论(0编辑  收藏  举报