From: Michael D. <mik...@us...> - 2005-04-11 03:45:48
|
Update of /cvsroot/nhibernate/nhibernate/doc/reference/en/modules In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv6045/modules Modified Files: association_mapping.xml collection_mapping.xml example_parentchild.xml Log Message: work on collections documentation Index: association_mapping.xml =================================================================== RCS file: /cvsroot/nhibernate/nhibernate/doc/reference/en/modules/association_mapping.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** association_mapping.xml 10 Apr 2005 14:37:17 -0000 1.1 --- association_mapping.xml 11 Apr 2005 03:45:37 -0000 1.2 *************** *** 13,16 **** --- 13,32 ---- <title>Introduction</title> <para> + Association mappings are often the most difficult thing to get right. In + this section we'll go through the canonical cases one by one, starting + with unidirectional mappings, and then considering the bidirectional cases. + We'll use <literal>Person</literal> and <literal>Address</literal> in all + the examples. The namespace and assembly are not included in the example + mappings to keep the mappings focused on the important aspects. + </para> + <para> + We'll classify associations by wheter or not they map to an intervening + join table, and by multiplicity. + </para> + <para> + Nullable foreign keys are not considered good practive in traditional data + modeling, so all our examples use not null foreign keys. This is not a + requirement of NHibernate, and the mappings will all work if you drop the + nullability constraints. </para> </sect1> *************** *** 58,62 **** <sect2 id="assoc-bidirectional-m21"> <title>one to many / many to one</title> ! <para></para> </sect2> <sect2 id="assoc-bidirectional-121"> --- 74,115 ---- <sect2 id="assoc-bidirectional-m21"> <title>one to many / many to one</title> ! <para> ! A <emphasis>bidirectional one-to-many association</emphasis> is the ! most common kind of association. (This is the standard parent/child ! relationship.) ! </para> ! <programlisting><![CDATA[<class name="Person"> ! <id name="Id" column="personId"> ! <generator class="native" /> ! </id> ! <many-to-one name="Address" ! column="addressId" ! not-null="true" ! /> ! </class> ! ! <class name="Address"> ! <id name="Id" column="addressId"> ! <generator class="native" /> ! </id> ! <set name="People" inverse="true"> ! <key column="addressId" /> ! <one-to-many class="Person" /> ! </set> ! </class>]]></programlisting> ! ! <programlisting><![CDATA[ ! create table Person ! ( ! personId bigint not null primary key, ! addressId bigint not null ! ) ! ! create table Address ! ( ! addressId bigint not null primary key ! ) ! ! ]]></programlisting> </sect2> <sect2 id="assoc-bidirectional-121"> *************** *** 70,74 **** <sect2 id="assoc-bidirectional-join-12m"> <title>one to many / many to one</title> ! <para></para> </sect2> <sect2 id="assoc-bidirectional-join-121"> --- 123,128 ---- <sect2 id="assoc-bidirectional-join-12m"> <title>one to many / many to one</title> ! <para> ! </para> </sect2> <sect2 id="assoc-bidirectional-join-121"> Index: example_parentchild.xml =================================================================== RCS file: /cvsroot/nhibernate/nhibernate/doc/reference/en/modules/example_parentchild.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** example_parentchild.xml 8 Apr 2005 19:23:35 -0000 1.1 --- example_parentchild.xml 11 Apr 2005 03:45:37 -0000 1.2 *************** *** 0 **** --- 1,274 ---- + <!-- + before committing make sure to comment out the DOCTYPE + It is in here to get intellisense with XMLSpy. The + HomeEdition is a free download. + + <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "../../support/docbook-dtd/docbookx.dtd"> + --> + <chapter id="example-parentchild"> + <title>Example: Parent/Child</title> + + <para> + One of the very first things that new users try to do with NHibernate is to model a parent / child type + relationship. There are two different approaches to this. For various reasons the most convenient + approach, especially for new users, is to model both <literal>Parent</literal> and <literal>Child</literal> + as entity classes with a <literal><one-to-many></literal> association from <literal>Parent</literal> + to <literal>Child</literal>. (The alternative approach is to declare the <literal>Child</literal> as a + <literal><composite-element></literal>.) Now, it turns out that default semantics of a one to many + association (in NHibernate) are much less close to the usual semantics of a parent / child relationship than + those of a composite element mapping. We will explain how to use a <emphasis>bidirectional one to many + association with cascades</emphasis> to model a parent / child relationship efficiently and elegantly. It's + not at all difficult! + </para> + + <sect1 id="example-parentchild-collections"> + <title>A note about collections</title> + <para> + NHibernate collections are considered to be a logical part of the their owning entity; never of the + contained entities. This is a crucial distinction! It has the following consequences: + </para> + + <itemizedlist> + <listitem> + <para> + When we remove/add an object from/to a collection, the version number of the collection owner + is incremented. + </para> + </listitem> + <listitem> + <para> + If an object that was removed from a collection is an instance of a value type (eg, a composite + element), that object will cease to be persistent and its state will be completely removed from + the database. Likewise, adding a value type instance to the collection will cause its state to + be immediately persistent. + </para> + </listitem> + <listitem> + <para> + On the other hand, if an entity is removed from a collection (a one-to-many or many-to-many + association), it will not be deleted, by default. This behavior is completely consistent - a + change to the internal state of another entity should not cause the associated entity to vanish! + Likewise, adding an entity to a collection does not cause that entity to become persistent, by + default. + </para> + </listitem> + </itemizedlist> + + <para> + Instead, the default behavior is that adding an entity to a collection merely creates a link between + the two entities, while removing it removes the link. This is very appropriate for all sorts of cases. + Where it is not appropriate at all is the case of a parent/child relationship, where the life of the + child is bound to the lifecycle of the parent. + </para> + </sect1> + + <sect1 id="example-parentchild-bidir"> + <title>Bidirectional one-to-many</title> + <para> + Suppose we start with a simple <literal><one-to-many></literal> association from + <literal>Parent</literal> to <literal>Child</literal>. + </para> + + <programlisting><![CDATA[<set name="Children"> + <key column="parent_id" /> + <one-to-many class="Child" /> + </set>]]></programlisting> + + <para> + If we were to execute the following code + </para> + + <programlisting><![CDATA[Parent p = session.Load( typeof( Parent ), pid ) as Parent; + Child c = new Child(); + p.Children.Add( c ); + session.Save( c ); + session.Flush(); + ]]></programlisting> + + <para> + NHibernate would issue two SQL statements: + </para> + + <itemizedlist> + <listitem> + <para>an <literal>INSERT</literal> to create the record for <literal>c</literal></para> + </listitem> + <listitem> + <para> + an <literal>UPDATE</literal> to create the link from <literal>p</literal> to + <literal>c</literal> + </para> + </listitem> + </itemizedlist> + + <para> + This is not only inefficient, but also violates any <literal>NOT NULL</literal> constraint on the + <literal>parent_id</literal> column. + </para> + + <para> + The underlying cause is that the link (the foreign key <literal>parent_id</literal>) from + <literal>p</literal> to <literal>c</literal> is not considered part of the state of the <literal>Child</literal> + object and is therefore not created in the <literal>INSERT</literal>. So the solution is to make the link part + of the <literal>Child</literal> mapping. + </para> + + <programlisting><![CDATA[<many-to-one name="Parent" column="parent_id" not-null="true"]]></programlisting> + + <para> + (We also need to add the <literal>Parent</literal> property to the <literal>Child</literal> class.) + </para> + + <para> + Now that the <literal>Child</literal> entity is managing the state of the link, we tell the collection not + to update the link. We use the <literal>inverse</literal> attribute. + </para> + + <programlisting><![CDATA[<set name="Children" inverse="true"> + <key column="parent_id" /> + <one-to-many class="Child" /> + </set>]]></programlisting> + + <para> + The following code would be used to add a new <literal>Child</literal>. + </para> + + <programlisting><![CDATA[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(); + ]]></programlisting> + + <para> + An now, only one SQL <literal>INSERT</literal> would be issued! + </para> + + <para> + To tighten things up a bit, we could create an <literal>AddChild()</literal> method in + <literal>Parent</literal>. + </para> + + <programlisting><![CDATA[public void AddChild( Child c ) + { + this.Children.Add( c ); + c.Parent = this; + } + ]]></programlisting> + + <para> + The <literal>AddChild</literal> method would simplify the code to + </para> + + <programlisting><![CDATA[Parent p = session.Load( typeof( Parent ), pid ) as Parent; + Child c = new Child(); + p.AddChild( c ); // + session.Save( c ); + session.Flush(); + ]]></programlisting> + + </sect1> + + <sect1 id="example-parentchild-cascades"> + <title>Cascading lifecycle</title> + + <para> + The explicit call to <literal>Save()</literal> is still annoying. We will address this by + using cascades. + </para> + + <programlisting><![CDATA[<set name="Children" inverse="true" cascade="all"> + <key column="parent_id" /> + <one-to-many class="Child" /> + </set>]]></programlisting> + + <para> + This simplifies the code above to + </para> + + <programlisting><![CDATA[Parent p = session.Load( typeof( Parent ), pid ) as Parent; + Child c = new Child(); + p.AddChild( c ); + session.Flush(); + ]]></programlisting> + + <note> + <para> + Cascades depend heavily upon the <literal>unsaved-value</literal> attribute. Please ensure that + the default value of the <literal><id></literal> property is the same as + the <literal>unsaved-value</literal>. + </para> + </note> + + <para> + Similarily, we don't need to iterate over the children when saving or deleting a <literal>Parent</literal>. + The following removes <literal>p</literal> and all its children from the database. + </para> + + <programlisting><![CDATA[Parent p = session.Load( typeof( Parent ), pid ) as Parent; + session.Delete( p ); + session.Flush(); + ]]></programlisting> + + <para> + However, this code + </para> + + <programlisting><![CDATA[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();]]></programlisting> + + <para> + will not remove <literal>c</literal> from the database; it will only remove the link to <literal>p</literal> + (and cause a <literal>NOT NULL</literal> constraint violation, in this case). You need to explicitly + <literal>Delete()</literal> the <literal>Child</literal>. + </para> + + <programlisting><![CDATA[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();]]></programlisting> + + <para> + Now, in our case, a <literal>Child</literal> can't really exist without its parent. So if we remove + a <literal>Child</literal> from the collection, we really want it to be deleted. For this, we must + use <literal>cascade="all-delete-orphan"</literal>. + </para> + + <programlisting><![CDATA[<set name="Children" inverse="true" cascade="all-delete-orphan"> + <key column="parent_id" /> + <one-to-many class="Child" /> + </set>]]></programlisting> + + <para> + Note: even though the collection mapping specifies <literal>inverse="true"</literal>, cascades are still + processed by iterating the collection elements. So if you require that an object be saved, deleted or + updated by cascade, you must add it to the collection. It is not enough to simply call the + <literal>Child.Parent</literal> setter. + </para> + </sect1> + <sect1 id="example-parentchild-update"> + <title>Using cascading <literal>update()</literal></title> + <para></para> + </sect1> + </chapter> \ No newline at end of file Index: collection_mapping.xml =================================================================== RCS file: /cvsroot/nhibernate/nhibernate/doc/reference/en/modules/collection_mapping.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -C2 -d -r1.3 -r1.4 *** collection_mapping.xml 10 Apr 2005 14:41:51 -0000 1.3 --- collection_mapping.xml 11 Apr 2005 03:45:37 -0000 1.4 *************** *** 13,19 **** <para> This section does not contain much example .NET code. We assume you already know ! how to use .NET's collections framework. If so, there's not really anything more ! to know - with a single caveat, you may use .NET collections the same way you ! always have. </para> <para> --- 13,19 ---- <para> This section does not contain much example .NET code. We assume you already know ! how to use .NET's collections framework and the concepts behind a Set collection. ! If so, there's not really anything more to know - with a single caveat, you may ! use collections the same way you always have. </para> <para> *************** *** 30,39 **** implementing the collection interface (eg. iteration order of a <literal>Iesi.Collections.ListSet</literal>. The persistent collections actually behave like ! <literal>System.Collections.Dictionary</literal>, <literal>System.Collections.ArrayList</literal>, ! <literal>Iesi.Collections.Set</literal> respectively. Furthermore, the .NET type of a property holding a collection must be the interface type (ie. <literal>IDictionary</literal>, <literal>IList</literal>, or ! <literal>IList</literal>). This restriction exists because NHibernate replaces your instances of <literal>IDictionary</literal>, <literal>IList</literal>, and <literal>ISet</literal> with instances of its own persistent implemetations of those --- 30,39 ---- implementing the collection interface (eg. iteration order of a <literal>Iesi.Collections.ListSet</literal>. The persistent collections actually behave like ! <literal>System.Collections.Hashtable</literal>, <literal>System.Collections.ArrayList</literal>, ! <literal>Iesi.Collections.HashedSet</literal> respectively. Furthermore, the .NET type of a property holding a collection must be the interface type (ie. <literal>IDictionary</literal>, <literal>IList</literal>, or ! <literal>ISet</literal>). This restriction exists because NHibernate replaces your instances of <literal>IDictionary</literal>, <literal>IList</literal>, and <literal>ISet</literal> with instances of its own persistent implemetations of those |