您的位置:首页 > 其它

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

2006-03-20 16:43 519 查看
一对多(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
/// 创建日期: 2006-3-17
/// 修 改 人:
/// 修改日期:
/// 修改内容:
/// 版 本:
public class Category
{
private Guid _categoryId;

private string _name = string.Empty;

private IList _items;

public Guid CategoryID
{
get { return this._categoryId; }
set { this._categoryId = value; }
}

public string Name
{
get { return this._name; }
set { this._name = value; }
}

public IList Items
{
get { return this._items; }
set { this._items = value; }
}

#region 构造函数
/// <summary>
/// 默认无参构造函数
/// </summary>
/// 创 建 人: Aero
/// 创建日期: 2006-3-17
/// 修 改 人:
/// 修改日期:
/// 修改内容:
public Category()
{
this._items = new ArrayList();
}

#endregion
}
}

一对多的关系在.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 =
session.CreateCriteria(typeof(Category)).List()[0] as Category;

// that works?
Assert.AreEqual(expectedCategory.Items.GetType(), typeof(System.Collections.ArrayList));
}
}

2、Item的POCO类:

1 using System;
2
3 namespace TestOne2Many
4 {
5 /// <summary>
6 /// Item 的摘要说明
7 /// </summary>
8 /// 创 建 人: Aero
9 /// 创建日期: 2006-3-17
/// 修 改 人:
/// 修改日期:
/// 修改内容:
/// 版 本:
public class Item
{
private Guid _itemId;

private string _name = string.Empty;

private Category _category;

public Guid ItemID
{
get { return this._itemId; }
set { this._itemId = value; }
}

public string Name
{
get { return this._name; }
set { this._name = value; }
}

public Category Category
{
get { return this._category; }
set { this._category = value; }
}

#region 构造函数
/// <summary>
/// 默认无参构造函数
/// </summary>
/// 创 建 人: Aero
/// 创建日期: 2006-3-17
/// 修 改 人:
/// 修改日期:
/// 修改内容:
public Item()
{
//
// TODO: 在此处添加构造函数逻辑
//
}

#endregion
}
}

二、准备数据库
新建数据库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" />
<bag name="Items" lazy="true" inverse="true" cascade= "all-delete-orphan">
<key column="CategoryID" />
<one-to-many class="TestOne2Many.Item, TestOne2Many" />
</bag>
</class>

<class name="TestOne2Many.Item, TestOne2Many" table="nh_items">
<id name="ItemID" column="ItemID" type="Guid"
unsaved-value="00000000-0000-0000-0000-000000000000">
<generator class="guid" />
</id>

<property name="Name" type="string" length="50" />
<many-to-one name="Category" class="TestOne2Many.Category, TestOne2Many"
column="CategoryID" />
</class>

</hibernate-mapping>

部分配置节点的含义和用法在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
Category expectedCategory = new Category();
expectedCategory.Name = "category" + System.Environment.TickCount.ToString();

for (int i = 0; i < 10; i++)
{
Item item = new Item();
item.Name = "item" + System.Environment.TickCount.ToString();
item.Category = expectedCategory;

expectedCategory.Items.Add(item);
}

// save objects in a all-cascading way
// note: cascading option should at least set as "all" in objects.hbm.xml
ITransaction trans = session.BeginTransaction();

try
{
session.SaveOrUpdate(expectedCategory);
trans.Commit();
}
catch
{
trans.Rollback();
throw;
}

// that works?
Category actualCategory =
session.Get(typeof(Category), expectedCategory.CategoryID) as Category;
Assert.IsNotNull(actualCategory);
Assert.AreEqual(expectedCategory.Items.Count, actualCategory.Items.Count);
}
}

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,
// see initialization in TestCategory.TestInitialize().
// note: cascading option should set as "all-delete-orphan" in objects.hbm.xml
Category expectedCategory =
session.CreateCriteria(typeof(Category)).List()[0] as Category;
int expectedItemCount = expectedCategory.Items.Count;

// execute a cascading update
ITransaction trans = session.BeginTransaction();

try
{
// remove an item from item-collection from the repository
expectedCategory.Items.RemoveAt(0);

session.Update(expectedCategory);
trans.Commit();
}
catch (System.Exception e)
{
trans.Rollback();
throw;
}

// that works?
Assert.AreEqual(1, session.CreateCriteria(typeof(Category)).List().Count);
Assert.AreEqual(expectedItemCount - 1, session.CreateCriteria(typeof(Item)).List().Count);
}
}

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,
// see initialization in TestCategory.TestInitialize().
// note: cascading option should set as "all-delete-orphan" in objects.hbm.xml
Category expectedCategory =
session.CreateCriteria(typeof(Category)).List()[0] as Category;

// remove category from the repository
ITransaction trans = session.BeginTransaction();

try
{
session.Delete(expectedCategory);
trans.Commit();
}
catch (System.Exception e)
{
trans.Rollback();
throw;
}

// that works?
Assert.AreEqual(0, session.CreateCriteria(typeof(Category)).List().Count);
Assert.AreEqual(0, session.CreateCriteria(typeof(Item)).List().Count);
}
}

要特殊指出的是,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 =
session.CreateCriteria(typeof(Category)).List()[0] as Category;
int expectedItemCount = expectedCategory.Items.Count;

// execute a cascading update
ITransaction trans = session.BeginTransaction();

try
{
// we can't remove all items by dereference Category.Items as null,
// this will cause a NHiberate.HibernateException
expectedCategory.Items = null;

// still we can't remove items in the following way,
// this will cause a NullReference Exception from NHibernate

//expectedCategory.Items[0] = null;

session.Update(expectedCategory);
trans.Commit();
}
catch (System.Exception e)
{
trans.Rollback();
throw;
}

// that works?
Assert.AreEqual(1, session.CreateCriteria(typeof(Category)).List().Count);
Assert.AreEqual(expectedItemCount, session.CreateCriteria(typeof(Item)).List().Count);
}
}

完整示例代码可从ObjectMappings.rar下载,其中的TestOne2Many即本文所讨论的工程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: