[NHibernate]Parent/Child

系列文章


[Nhibernate]体系结构

[NHibernate]ISessionFactory配置

[NHibernate]持久化类(Persistent
Classes)

[NHibernate]O/R
Mapping基础

[NHibernate]集合类(Collections)映射 

[NHibernate]关联映射

引言

刚刚接触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的一对多关系。

1 <set name="Children">
2 <key column="parent_id" />
3 <one-to-many class="Child" />
4 </set>

如果我们运行下面的代码

1 Parent p = session.Load( typeof( Parent ), pid ) as Parent;
2 Child c = new Child();
3 p.Children.Add( c );
4 session.Save( c );
5 session.Flush();

NHibernate就会产生下面的两条sql语句:

  • 一条Ineset语句,用于创建对象c对应的数据库记录。

  • 一条update语句,用于创建从对象p到对象c的连接。

这样做不仅效率低,而且违反了列parent_id非空的限制。

底层的原因是,对象p到对象c的连接(外键parent_id)没有被当作是child对象状态的一部分,也没有在insert的时候被创建。解决的办法是,在child一端设置映射。

1 <many-to-one name="Parent" column="parent_id" not-null="true"

(我们还需要为类child添加parent属性)

现在实体child在管理连接的状态,为了使collection不更新连接,我们使用inverse属性。

1 <set name="Children" inverse="true">
2 <key column="parent_id" />
3 <one-to-many class="Child" />
4 </set>

下面的代码是用来添加一个新的child

1 Parent p = session.Load( typeof( Parent ), pid ) as Parent;
2 Child c = new Child();
3 c.Parent = p;
4 p.Children.Add( c );
5 session.Save( c );
6 session.Flush();

现在,只会有一条insert语句被执行!

为了让事情变得井井有条,可以为parent加一个addchild()方法。

1 public void AddChild( Child c )
2 {
3 this.Children.Add( c );
4 c.Parent = this;
5 }

AddChild把代码简化了

1 Parent p = session.Load( typeof( Parent ), pid ) as Parent;
2 Child c = new Child();
3 p.AddChild( c ); //
4 session.Save( c );
5 session.Flush();

级联生命周期(Cascading lifecycle)

对每个对象调用Save()方法很麻烦,我们可以用级联来解决这个问题。

1 <set name="Children" inverse="true" cascade="all">
2 <key column="parent_id" />
3 <one-to-many class="Child" />
4 </set>

配置级联以后,代码就简化了:

1 Parent p = session.Load( typeof( Parent ), pid ) as Parent;
2 Child c = new Child();
3 p.AddChild( c );
4 session.Flush();

注意

级联四份依赖unsaved-value属性(attribute)。请确保<id>属性(property)的默认值和unsaved-value一样。类似的,当保存和删除Parent时我们不需要办理children。下面的代码从数据库中删除了p和它所有的children。

1 Parent p = session.Load( typeof( Parent ), pid ) as Parent;
2 session.Delete( p );
3 session.Flush();

然而,这段代码


 1 Parent p = session.Load( typeof( Parent ), pid ) as Parent;
2 Child c = null;
3 foreach( Child child in p.Children )
4 {
5 c = child; // only care about first Child
6 break;
7 }
8 p.Children.Remove( c );
9 c.Parent = null;
10 session.Flush();

不会从数据库删除c,它只会删除与p之间的连接(并且会导致违反NOT
NULL约束,在这个例子中)。你需要明确调用Child的Delete()方法。


 1 Parent p = session.Load( typeof( Parent ), pid ) as Parent;
2 Child c = null;
3 foreach( Child child in p.Children )
4 {
5 c = child; // only care about first Child
6 break;
7 }
8 p.Children.Remove( c );
9 c.Parent = null;
10 session.Delete( c );
11 session.Flush();

在我们的例子中,如果我们规定没有父对象的话,子对象就不应该存在,如果将子对象从collection中移除,实际上我们是想删除它。要实现这种要求,就必须使用cascade=“all-delete-orphan”.

1 <set name="Children" inverse="true" cascade="all-delete-orphan">
2 <key column="parent_id" />
3 <one-to-many class="Child" />
4 </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对象。

1 //parent and child were both loaded in a 上一页ious session
2 parent.AddChild( child );
3 Child newChild = new Child();
4 parent.AddChild( newChild );
5 session.Update( parent );
6 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,布布扣,bubuko.com

时间: 2024-11-03 21:43:40

[NHibernate]Parent/Child的相关文章

XPath学习:parent,child

XPath 是一门在 XML 文档中查找信息的语言.XPath 可用来在 XML 文档中对元素和属性进行遍历. XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 同时被构建于 XPath 表达之上. 推荐一个挺不错的网站:http://www.zvon.org/xxl/XPathTutorial/General_chi/examples.html  里面有很不错的例子,下面的例子中红色字体表示使用对应语法获取的元素(或属性). XPath轴(XPath

SSAS parent/child dimension

ex: EmployeeKey ParentEmployeeKey FullName . 给层级命名: ParentEmployeeKey-->Properties--> NameTemplate  定义层级名称 . Browse中如果想看到FullName,EmployeeKey-->Properties-->NameColumn: FullName . 上层member不显示在下一层:ParentEmployeeKey-->Properties-->MemberWi

[NHibernate]存储过程的使用(三)

目录 写在前面 文档与系列文章 查询 总结 写在前面 前面的文章介绍了在nhibernate中使用存储过程进行增删改的操作,当然查询也是可以的,在nhibernate中也可以执行任意的存储过程.本篇文章将介绍如何使用查询的存储过程的方式. 文档与系列文章 [Nhibernate]体系结构 [NHibernate]ISessionFactory配置 [NHibernate]持久化类(Persistent Classes) [NHibernate]O/R Mapping基础 [NHibernate]

[NHibernate]立即加载

目录 写在前面 文档与系列文章 立即加载 一个例子 总结 写在前面 上篇文章介绍了nhibernate延迟加载的相关内容,简单回顾一下延迟加载,就是需要的时候再去加载,需要的时候再向数据库发出sql指令进行查询. 本篇文章介绍的立即加载,则和延迟加载相对,举个简单的例子,就是查询客户信息,则会将该客户相关联的数据立即进行加载.实现立即加载的方式有三种:设置映射文件中节点的可选属性lazy,Nhibernate提供的实用类,HQL抓取策略. 文档与系列文章 [Nhibernate]体系结构 [NH

[NHibernate]延迟加载

目录 写在前面 文档与系列文章 延迟加载 一个例子 总结 写在前面 上篇文章介绍了多对多关系的关联查询的sql,HQL,Criteria查询的三种方式.本篇文章将介绍nhibernate中的延迟加载方式,延迟加载按个人理解也可以叫做按需要加载(Loading-on-demand). 文档与系列文章 [Nhibernate]体系结构 [NHibernate]ISessionFactory配置 [NHibernate]持久化类(Persistent Classes) [NHibernate]O/R

[NHibernate]增删改操作

目录 写在前面 文档与系列文章 添加数据 删除数据 修改数据 添加修改数据 总结 写在前面 上篇文章介绍了nhibernate的基于面向对象的条件查询.对一个项目来说,增删改查是必不可少的,虽然实现方式不同,但是总有涉及到这部分的代码.之前跟朋友说过一个笑话,你要会增删改查了,一切问题就不是问题了,在加上业务处理基本上就完成一个项目了.可能说的是有点过了,但是我觉得在编码中,重要的是你的业务处理能力,在公司很少人能能站在项目框架或者架构的决策的位置,这个时候,作为开发来说不就是负责模块吗?而这些

耗时两月,NHibernate系列出炉

写在前面 这篇总结本来是昨天要写的,可昨天大学班长来视察工作,多喝了点,回来就倒头就睡了,也就把这篇总结的文章拖到了今天. nhibernate系列从开始着手写,到现在前后耗费大概两个月的时间,通过总结这个系列,确实收获不小,这里将本系列的导航,列出放在这里算是对nhibernate系列的一个小总结,也方便想学习nhibernate的朋友查找. nhibernate文档 文档部分是从网上搜集,认为还是比较全面的,就在博客上做了记录,也希望在用到的时候,有个地方可以方便的查找. [Nhiberna

[NHibernate]代码生成器的使用

目录 写在前面 文档与系列文章 代码生成器的使用 总结 写在前面 前面的文章介绍了nhibernate的相关知识,都是自己手敲的代码,有时候显得特别的麻烦,比如你必须编写持久化类,映射文件等等,举得例子比较简单,字段比较少,如果一个数据表有几十个字段,你手敲的话烦都烦死了,不过为了学习nhibernate的语法,初学的过程还是建议自己动手,这样敲的多了就对每个过程,及映射文件中的节点的含义有个大致印象.前面举的例子,你也会发现这种方式的步骤就是首先创建数据库,然后编写持久化类,映射文件,最后编写

[NHibernate]多对多关系(关联查询)

目录 写在前面 文档与系列文章 多对多关系关联查询 总结 写在前面 上篇文章介绍了nhibernate中对一对多关系进行关联查询的几种方式,以及在使用过程需要注意的问题.这篇文章对多对多关系的查询处理也采用上篇文章的描述方式进行说明. 文档与系列文章 [Nhibernate]体系结构 [NHibernate]ISessionFactory配置 [NHibernate]持久化类(Persistent Classes) [NHibernate]O/R Mapping基础 [NHibernate]集合