根据 约定,本文将通过Category和Item对象来描述one2many的关系,即一个Category对象对应多个Item对象。
主要内容:
1、编写POCO类
2、准备数据库
3、编写配置文件
4、级连(cascading)操作示例
一、编写POCO类
从手记(6)起我打算由编写POCO类开始描述,因为NHibernate已经让我们以对象的方式去思考数据操作,数据表该怎么设计已经不是思维的起点,更不是重点。
1、Category的POCO类:
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
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类:
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)
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的运行配置信息。
< 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文件中。
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
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 | 进行级连save和update操作 | ||
delete | 进行级连删除操作 | ||
delete-orphan | 删除无相关的父对象的子对象 | ||
all | 进行级连save/update/delete操作 | ||
all-delete-orphan | all + delete-orphan |
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对象
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子对象从数据库中删除。
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对象也被删除。
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对象。
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即本文所讨论的工程。