一对多(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()[
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,数据库设计如下:
或直接执行以下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 |
进行级连save和update操作 |
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即本文所讨论的工程。