NHibernate一对多(Parent/Child)实例

副标题:关于级联生命周期和更新的介绍(inverse和cascade)

最近学习NHibernate,我发现相对于其它技术,Nhibernate相关文章较少,而且介绍的不是很理想。回头肯英文文献,发现收获甚多。现在,我把Nhibernate参考文献NHibernate一对多(Parent/Child)实例一节翻译如下。本文并没有逐字翻译,有的翻译可能还不到位,还请批评指正。

原英文地址:http://nhforge.org/doc/nh/en/index.html#example-parentchild

 

新手学习NHibernate首先要做的事情之一就是对parent/child类型关系进行建模。这种模型有两种不同的实现方式。出于各种原因,对于新手而言最方便的方式是将Parent和Child作为实体类,并通过<one-to-many>关联Parent和Child;另一种方式是将Child声明为<composite-element>。但现已证明,通常的parent/child 关系的语义用NHibernate中的组合元素(composite element)映射比用一对多关系的实现方式更为接近。

本文将尽量有效和简洁地解释怎样使用一个双向的一对多关联进行parent/child建模。毕竟,这本身很简单!

一.      NHibernate集合概述

NHibernate中的集合是作为所属实体类的逻辑部分而存在的,而不是包含的实体集(NHibernatecollections are considered to be a logical part of their owning entity; neverof the contained entities.)。这是主要的区别!其具有以下的几点结论:

1.  当我们从一个集合中 删除/添加 一个对象时,集合拥有者的版本号将增加。

2.  如果从集合中移除的一个对象是一个值类型(如一个组合元素),那么该对象将不再持久,并且其状态将完全从数据库中删除。同理,在集合中添加一个值实例,其状态将立刻被持久化。

3.  另一方面,如果一个实体从集合中移除(无论是one-to-many还是many-to-many关系),默认情况下它将不会被删除。这种行为完全一致的——另一实体的一种内部状态的改变不会导致该关联的实体消失!(This behavior is completely consistent - a change to the internal stateof another entity should not cause the associated entity to vanish! )同理,默认情况下,添加一个实体到集合中并不导致该实体成为持久对象。(Likewise, adding an entity to acollection does not cause that entity to become persistent, by default. )

(个人理解:上述第二点是讲的是理想情况,而第三点是实际情况)

默认的情况下,集合中添加一个实体,只是简单地创建了两个实体间的链接,而移除实体也只是移除了对应的链接。这对于所有具有类型情况都很实用。但是对于parent/child关系并不适用,它使child的生命周期绑定到了parent。

二.      双向的one-to-many

我们以简单的“家长—孩子”的一对多(<one-to-many>)关系开始。

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

如果我们想执行以下代码:

Parent p = .....;
Child c = new Child();
p.Children.Add(c);
session.Save(c);
session.Flush();
 

NHibernate 将执行以下两个 SQL 语句:

·        一个创建存储c的 INSERT

·        一个创建p到c关联的 UPDATE

这种方式不仅效率低,而且违背了字表中外键列(parent_id)的Not NULL限制。

出现上述情况的根本原因是,在Child对象中没有被考虑从p到c的关联(parent_id外键),因此没有在INSERT中创建,因此解决方法是将该关联写在Child映射文件中。

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

同样,我们也需要在
Parent属性中加入Child类。

现在,既然Child实体已经负责管理关联的状态了,我们让Parent中的集合属性不用更新该关联了。在此,我们使用set 的inverse属性。

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

以下的代码可用于添加一个新Child。

Parent p = (Parent) session.Load(typeof(Parent), pid);
Child c = new Child();
c.Parent = p;
p.Children.Add(c);
session.Save(c);
session.Flush();
 

这样,只有SQL INSERT 执行了!

更近一步,我们为Parent创建一个AddChild()方法。


public void AddChild(Child c)
{
    c.Parent = this;
    children.Add(c);
}
那么,添加一个Child的代码就是这样了:
Parent p = (Parent) session.Load(typeof(Parent), pid);
Child c = new Child();
p.AddChild(c);
session.Save(c);
session.Flush(); 

三.      级联生命周期(Cascading lifecycle)

显示的调用Save()方法总是很恼人。我们通过使用cascades来说明。


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

采用简化的代码如下:

Parent p = (Parent) session.Load(typeof(Parent), pid);
Child c = new Child();
p.AddChild(c);
session.Flush();

同理,当保存和修改一个 Parent时,我们并不需要遍历其children。以下代码是从数据库中移除p和其所有的children。

Parent p = (Parent) session.Load(typeof(Parent), pid);
session.Delete(p);
session.Flush();


然而,下列代码:

Parent p = (Parent) session.Load(typeof(Parent), pid);
//在集合中移除一个child
IEnumerator childEnumerator = p.Children.GetEnumerator();
childEnumerator.MoveNext();
Child c = (Child) childEnumerator.Current;
 
p.Children.Remove(c);
c.Parent = null;
session.Flush();


不能从数据库中移除c;它只移除与p关联的链接(在此将引发NOT NULL约束冲突)。你需要显示的调用Child的 Delete()方法。

Parent p = (Parent) session.Load(typeof(Parent), pid);
//在集合中移除一个child
IEnumerator childEnumerator = p.Children.GetEnumerator();
childEnumerator.MoveNext();
Child c = (Child) childEnumerator.Current;
 
p.Children.Remove(c);
session.Delete(c);
session.Flush();


现在,在我们的例子中,一个Child不能脱离其Parent而存在。因此如果我们从集合中移除一个Child,我们真实想法是要删除它。对于此,我们必须使用

 cascade="all-delete-orphan". 
<set name="Children" inverse="true" cascade="all-delete-orphan">
    <key column="parent_id"/>
    <one-to-many class="Child"/>
</set>
 
注意:即使集合映射指定了inverse="true", cascades将仍然被通过遍历集合元素而执行。因此如果你需要通过 cascade 保存、删除或更新一个object,你必须添加其到集合中。简单地指定其patent是不够的。

 

四.      使用级联更新(Update()

如果我们在一个Isession中加载了一个Parent,在UI操作中对其属性做了改变,我们希望在新的Isession中持久化这些改变(通过调用Update())。Parent包含一个children集合,由于使用了级联更新,NHibernate需要知道哪些children是新实例化的,哪些是数据库中已经存在的。假设Parent和Child都具有一个long型属性。NHibernate将使用这个标识属性值来决定哪些children是新的。

unsaved-value通常用于作为一个新实例的标识值。在NHibernate中,不用显示指定unsaved-value。

以下代码将更新parent和child,并且插入newChild。

// parent 和 child是先前的session 加载的
parent.AddChild(child);
Child newChild = new Child();
parent.AddChild(newChild);
session.Update(parent);
session.Flush();


对于自动生成的标识符都可以用这种方法。但是对于指定的或者组合的标识符呢?这就比较复杂了,因为 unsaved-value 不能区别新实例(用户指定的标识符)和先前session加载的实例。这种情况下,你将需要给NHibernate提示,可采用以下方法之一:

·        在<version><timestamp>属性中定义一个unsaved-value  映射到这个类。

·        设置 unsaved-value="none" 并且在调用Update(parent)之前,显示调用 Save() 保存新的 children。

·        设置 unsaved-value="any"并且在调用Update(parent)之前,显示调用 Update ()更新先前持久的 children。

对于指定的标识符,unsaved-value默认的值是null;对于联合键是none。

还有一种可能性。有一个新IInterceptor方法叫IsTransient(),它通过这个方法使程序实现区别新实例。例如,为持久化类定义一个基类。

public class Persistent
{
    private bool _saved = false;
    
    public void OnSave()
    {
        _saved=true;
    }
    
    public void OnLoad()
    {
        _saved=true;
    }
    
    ......
    
    public bool IsSaved
    {
        get { return _saved; }
    }
}


其中,属性
saved 是非持久的。

 
现在实现IsTransient()OnLoad()OnSave(),代码如下:
public object IsTransient(object entity)
{
    if (entity is Persistent)
    {
        return !( (Persistent) entity ).IsSaved;
    }
    else
    {
        return null;
    }
}
 
public bool OnLoad(object entity, 
    object id,
    object[] state,
    string[] propertyNames,
    IType[] types)
{
    if (entity is Persistent) ( (Persistent) entity ).OnLoad();
    return false;
}
 
public boolean OnSave(object entity,
    object id,
    object[] state,
    string[] propertyNames,
    IType[] types)
{
    if (entity is Persistent) ( (Persistent) entity ).OnSave();
    return false;
}
 

五.      结语

消化这些内容是需要花些时间的,可能一开始读这篇文章还有点让人迷惑,然而实践应用中,这些的可行性都非常好。大多数NHibernate程序在很多地方都使用parent / child模式。

我们在第一段提到了一个可选方案。以上的讨论没有包含具有语义的parent / child关系的<composite-element>映射。不幸的是,组合元素类有两个限制:组合元素不能拥有集合,并且它们的parent只能唯一。(然而,它们可以有通过<idbag>映射使用一个代理主键)

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值