[NHibernate]Parent/Child
系列文章
[NHibernate]持久化类(Persistent Classes)
[NHibernate]集合类(Collections)映射
引言
刚刚接触NHibernate的人大多是从父子关系(parent/child type relationship)的建模入手的。父子关系的建模有两种方法。比较简便、直观的方法就是在实体类parent和child之间建立<one to many>的关联关系,从parent指向child,对新手来说尤其如此。但还有另一种方法,就是将child声明为一个<composite-element>(组合元素)。可以看出在NHibernate中使用一对多关联比composite element更接近于通常parent/child关系的语义。下面我们会阐述如何使用双向可级联的一对多关联(bidirectional one to many association with cascades)去建立有效、优美的parent/child关系。
关于collections
在NHibernate下,实体类将collection作为自己的一个逻辑单元,而不是被容纳的多个实体。这非常重要!它主要体现为以下几点:
- 当删除或增加collection中的实体对象的版本值会递增。
- 如果一个从collection中移除的对象是一个值类型(value type)的实例,比如composite element,那么这个对象的持久化状态将会终止,其在数据库中对应的记录会被删除。同样的,想collection增加一个value type的实例将会使之立即被持久化。
- 另一方面,如果从一对多或者多对多关联的collection中移除一个实体(一对多one-tomany或者多对多many-to-many关联),在缺省情况下这个对象并不会被删除。这个行为是完全合乎逻辑的——改变一个实体的内部状态不应该使与它关联的实体消失掉!同样的,向collection增加一个实体不会使之被持久化。
实际上,向Collection增加一个实体的缺省动作只是在两个实体之间创建一个连接而已,同样移除的时候也只是删除连接。这种处理对于所有的情况都是合适的。不适合所有情况的其实是父子关系本身,因为对象是否存在依赖于父对象的生存周期。
双向的一对多关系(Bidirectional one-to-many)
让我们从一个简单的例子开始,假设要实现一个从类Parent到类Child的一对多关系。
<set name="Children">
<key column="parent_id" />
<one-to-many class="Child" />
</set>
如果我们运行下面的代码
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = new Child();
p.Children.Add( c );
session.Save( c );
session.Flush();
NHibernate就会产生下面的两条sql语句:
- 一条Ineset语句,用于创建对象c对应的数据库记录。
- 一条update语句,用于创建从对象p到对象c的连接。
这样做不仅效率低,而且违反了列parent_id非空的限制。
底层的原因是,对象p到对象c的连接(外键parent_id)没有被当作是child对象状态的一部分,也没有在insert的时候被创建。解决的办法是,在child一端设置映射。
<many-to-one name="Parent" column="parent_id" not-null="true"
(我们还需要为类child添加parent属性)
现在实体child在管理连接的状态,为了使collection不更新连接,我们使用inverse属性。
<set name="Children" inverse="true">
<key column="parent_id" />
<one-to-many class="Child" />
</set>
下面的代码是用来添加一个新的child
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = new Child();
c.Parent = p;
p.Children.Add( c );
session.Save( c );
session.Flush();
现在,只会有一条insert语句被执行!
为了让事情变得井井有条,可以为parent加一个addchild()方法。
public void AddChild( Child c )
{
this.Children.Add( c );
c.Parent = this;
}
AddChild把代码简化了
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = new Child();
p.AddChild( c ); //
session.Save( c );
session.Flush();
级联生命周期(Cascading lifecycle)
对每个对象调用Save()方法很麻烦,我们可以用级联来解决这个问题。
<set name="Children" inverse="true" cascade="all">
<key column="parent_id" />
<one-to-many class="Child" />
</set>
配置级联以后,代码就简化了:
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = new Child();
p.AddChild( c );
session.Flush();
注意
级联依赖unsaved-value属性(attribute)。请确保<id>属性(property)的默认值和unsaved-value一样。类似的,当保存和删除Parent时我们不需要办理children。下面的代码从数据库中删除了p和它所有的children。
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
session.Delete( p );
session.Flush();
然而,这段代码
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = null;
foreach( Child child in p.Children )
{
c = child; // only care about first Child
break;
}
p.Children.Remove( c );
c.Parent = null;
session.Flush();
不会从数据库删除c,它只会删除与p之间的连接(并且会导致违反NOT NULL约束,在这个例子中)。你需要明确调用Child的Delete()方法。
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = null;
foreach( Child child in p.Children )
{
c = child; // only care about first Child
break;
}
p.Children.Remove( c );
c.Parent = null;
session.Delete( c );
session.Flush();
在我们的例子中,如果我们规定没有父对象的话,子对象就不应该存在,如果将子对象从collection中移除,实际上我们是想删除它。要实现这种要求,就必须使用cascade=“all-delete-orphan”.
<set name="Children" inverse="true" cascade="all-delete-orphan">
<key column="parent_id" />
<one-to-many class="Child" />
</set>
注意:即使在collection一方的映射中指定inverse="true",在遍历collection的时候级联操作仍然会执行。如果你想要通过级联进行子对象的插入、删除、更新操作,就必须把它叫到collection中,只调用Child.Paent的setter是不够的。
级联更新(Using cascading update())
假设我们从ISession中装入了一个Parent对象,用户界面对其进行了修改,然后我们希望在一个新的ISession里面调用Update()来更新它。对象Parent包含了子对象的集合,由于打开了级联更新,NHibernate需要知道哪些子对象是新的,哪些是数据库中已经存在的。我们假设Parent和Child对象的标识属性的类型为System.Int32。NHibernate会使用标识属性的值来判断哪些子对象是新的。(你也可以使用version或timestamp属性)
unsaved-value属性是用来表示新实例的标识属性值的,缺省为"null",对于.net的值类型(ValueTypes)这不是一个好的默认值,所以你需要unsaved-value。
如果我们使用原始类型作为标识类型的话,我们在配置child类映射的时候就必须写:
<id name="Id" type="Int64" unsaved-value="0">
对于child映射(也有unsaved-value属性(attribute)提供给版本(version)和时间戳(timestamp)属性(property)映射)。下面的代码会更新parent和child对象,并且插入newchild对象。
//parent and child were both loaded in a 上一页ious session
parent.AddChild( child );
Child newChild = new Child();
parent.AddChild( newChild );
session.Update( parent );
session.Flush();
好的,对于自动生成标识的情况这样做很方便,但是自分配的标识和复合标识怎么办呢?这是有点麻烦,因为unsaved-values无法区分新对象(标识是用户指定的)和前一个ISession装入的对象。在这种情况下,你可能需要给NHibernate一些提示,在待用update(parent)之前:
- 在这个类的<version>or <timestamp>属性映射上定义unsaved-value="null"或者unsaved-value=”negative“。
- 在对父对象执行Update(parent)之前,设定unsaved-value=”none“并且显式的调用Save()在数据库创建新子对象。
- 在对父对象执行Update(parent)之前,设定unsaved-value=”any“并且显式的调用Update()更新已经装入的子对象none是自动分配标识和复合标识的unsaved-value的缺省值。
总结
这个问题往往让新手感到迷惑,它确实不太容易消化。不过,经过一些实践后,你会感觉越来越顺手。父子对象模式已经被广泛的应用在NHibernate应用程序中。
在第一段中我们曾经提到另一个方案。复合元素的语义与父子关系是等同的,但是我们并没有详细讨论。很不幸复合元素还有两个重大限制:复合元素不能拥有collections,并且,除了用于唯一的父对象外,他们不能再作为其他任何实体的子对象。(但是,通过使用<idbag>映射,他们可能拥有代理主键。)
本文来自《NHibernate 中文文档》
[NHibernate]Parent/Child的更多相关文章
- 服务启动Apache服务,错误Parent: child process exited with status 3 -- Aborting.解决
不能启动apache,或者使用wamp等集成包后,唯独apache服务启动后有停止,但是把东西搬到其他机器上却没事问题可能和网络有关,我查了很多资料首先找打apache的错误报告日志,发现现实诸多的调 ...
- XPath学习:parent,child
XPath 是一门在 XML 文档中查找信息的语言.XPath 可用来在 XML 文档中对元素和属性进行遍历. XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointe ...
- csharp: DataRelation objects to represent a parent/child/Level relationship
/// <summary> /// /// </summary> /// <param name="sender"></param> ...
- 耗时两月,NHibernate系列出炉
写在前面 这篇总结本来是昨天要写的,可昨天大学班长来视察工作,多喝了点,回来就倒头就睡了,也就把这篇总结的文章拖到了今天. nhibernate系列从开始着手写,到现在前后耗费大概两个月的时间,通过总 ...
- [NHibernate]缓存(NHibernate.Caches)
系列文章 [Nhibernate]体系结构 [NHibernate]ISessionFactory配置 [NHibernate]持久化类(Persistent Classes) [NHibernate ...
- [NHibernate]NHibernate.Tool.hbm2net
系列文章 [Nhibernate]体系结构 [NHibernate]ISessionFactory配置 [NHibernate]持久化类(Persistent Classes) [NHibernate ...
- [NHibernate]Nullables
系列文章 [Nhibernate]体系结构 [NHibernate]ISessionFactory配置 [NHibernate]持久化类(Persistent Classes) [NHibernate ...
- [NHibernate]基本配置与测试
目录 写在前面 nhibernate文档 搭建项目 映射文件 持久化类 辅助类 数据库设计与连接配置 测试 总结 写在前面 一年前刚来这家公司,发现项目中使用的ORM是Nhibernate,这个之前确 ...
- [NHibernate]HQL查询
目录 写在前面 文档与系列文章 查询的几种方式 HQL查询 一个例子 总结 写在前面 上篇文章介绍了nhibernate在项目中的基本配置,包括数据库连接字符串的设置,映射文件的配置及需注意的地方,这 ...
随机推荐
- github的使用(概要版)
Github的世界 什么是github Github除提供Git仓库托管服务外,还为开发者或团队提供了一系列功能,帮助其高效率,高品质地进行代码编写. 使用github带来哪些变化 写作形式的变化 在 ...
- 我的CS考研路
说在前面 从去年7月15号正式准备考研以来,直到今天,3月19号,一共经历8个多月,考研初步告捷,在此想跟大家分享一下自己的经验,希望能对接下来考研的学弟学妹们有所帮助. 首先介绍下我自己的情况,本科 ...
- win7如何恢复以前的ie版本
如何恢复以前的ie版本-控制面板,程序和功能-查看已安装的更新-搜索Internet explorer,然后卸载更新就Ok.
- weka
第一次接触weka这个东西,貌似是数据挖掘用的比较多.接下来一个月里,希望能够对weka有更深一步的了解,并用博客记录下学习weka用法.原理.数据预处理的过程,与君分享,期待!
- Python-08-Socket
1. Python 网络编程 Python 提供了两个级别访问的网络服务: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接 ...
- Qt——动态库的创建和使用
一.动态库是什么 很多人写程序的人都见过.lib和.dll文件,对动态库也略有耳闻. 生成动态库后可以得到两个文件,后缀名分别是.lib以及.dll. 简而言之,.lib称为导入库,相当于头文件:.d ...
- ReactNative官方中文文档0.21
整理了一份ReactNative0.21中文文档,提供给需要的reactnative爱好者.ReactNative0.21中文文档.chm 百度盘下载:ReactNative0.21中文文档 来源: ...
- RequireJS中的require如何返回模块
requirejs中定义AMD模块规则如下: define(function(){ var ProductManager={ Create:function(){ console.log(" ...
- CSS基本知识5-CSS对齐
要对齐的关键,在于理解块,行的概念,块的对齐主要靠自动计算定位,比如margin:auto,及浮动,所以最好的办法是尽量使用行来对齐. 实例: .box { border: 1px solid #80 ...
- Android ORM 框架之 greenDAO 使用心得
前言 我相信,在平时的开发过程中,大家一定会或多或少地接触到 SQLite.然而在使用它时,我们往往需要做许多额外的工作,像编写 SQL 语句与解析查询结果等.所以,适用于 Android 的ORM ...