Ayende @ Rahien

Refunds available at head office

NHibernate Mapping -

Next up for inspection is the <many-to-one/> element. This element is defined as:

<many-to-one
        name="PropertyName"                                (1)
        column="column_name"                               (2)
        class="ClassName"                                  (3)
        cascade="all|none|save-update|delete"              (4)
        fetch="join|select"                                (5)
        update="true|false"                                (6)
        insert="true|false"                                (7)
        property-ref="PropertyNameFromAssociatedClass"     (8)
        access="field|property|nosetter|ClassName"         (9)
        unique="true|false"                                (10)
        optimistic-lock="true|false"                       (11)
        not-found="ignore|exception"                       (12)
/>

Much of the attributes on this element are identical to the ones that I outlined in the post about <property/>. 1, 2, 3, 6, 7, 9 and 11 are identical, and I am not going to cover them.

4) cascade is interesting, it controls one of the more interesting NHibernate features, persistence by reachability.I outlined all the possible options in 2006, so I wouldn’t repeat them again.

5) fetch is really interesting. Let us take a look at an entity definition, and explore how modifying it can alter NHibernate’s behavior.

<class name="Post"
		 table="Posts">
  
	<id name="Id">
		<generator class="identity"/>
	</id>
	<property name="Title" />
	<many-to-one name="Blog" column="BlogId"/>
</class>

And we have the following code to exercise NHibernate:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
	var post = session.Get<Post>(1);
	Console.WriteLine(post.Title);
	tx.Commit();
}

This would result in the following SQL.

image 

But interesting things happen when we start playing with the fetch attribute. Note that by default the fetch attribute is defaulting to “select”, so setting it to that value is merely making things explicit, but setting it to fetch=”join”, like this:

<many-to-one name="Blog" 
	column="BlogId"
	fetch="join"/>

Would result in the following SQL:

image

We eagerly load the Blog association, in this case.

8) property-ref is a legacy feature, it is meant to allow you to create many-to-one associations when the association is not done on the primary key of the association. In general, I would strongly suggest avoiding it.

9) unique is relevant only if you use NHibernate to specify your schema. This would generate a unique constraint when we generate the DDL.

12) not-found is another legacy feature, it controls how NHibernate behaves when it finds an invalid foreign key. That is, a value that points to an entity that doesn’t exist. By default, this would trigger an error, as this generally indicate a problem with the database, but with legacy database, you can tell it to set the property value to null instead.

Comments

pb
04/09/2009 02:41 AM by
pb

I've found a few problems when using property-ref and natural keys so I would heartily recommend adding a column for a surrogate key when using nhibernate. It all works pretty well with surrogate keys.

huey
04/09/2009 12:06 PM by
huey

Sucks that I've had to use both property-ref and not-found ... stupid legacy dbs!

Miki Watts
04/09/2009 01:06 PM by
Miki Watts

About not-found, is there a way to specify a different default value in case the foreign key is not found ? I've got a legacy database that has missing values, and doesn't allow nulls either.

Steve
04/09/2009 01:36 PM by
Steve

Thanks for your series so far, it's good to see this.

Steve
04/09/2009 02:13 PM by
Steve

Ayende, I have a question:

Let's say I have a 'Employee' - it has a many to one mapping to a Company

The Company in turn has a many to one to let's say a 'address'

Basically, if I set all these many -to-one's to 'fetch=join'

Will it propagate down when I get an employee ?

Meaning, when I ask for an employee it will join to company and company join to address in one call ?

Thanks

Ayende Rahien
04/09/2009 02:59 PM by
Ayende Rahien

Miki,

For that, you would need a custom IUserType to manage that.

LukeB
04/09/2009 03:16 PM by
LukeB

Ayende - I posted this to nhusers about using "fetch='join'" vs FetchMode.Eager

http://xrl.us/beoeep

Basically, setting fetch='join' has the desired effect, but setting FetchMode.Eager in my criteria doesn't have the same effect and I would expect that it does. I must be doing something slightly wrong.

Thanks - Luke

Ayende Rahien
04/09/2009 03:20 PM by
Ayende Rahien

That is because there is a difference between Eager and Join.

You should use Join or Select vs. Eager or Lazy

LukeB
04/09/2009 04:43 PM by
LukeB

Ayende, thanks for your response. The FetchMode enum defines "Eager" and "Join" to be equivalent. Using "FetchMode.Join" still does not use a join in the output SQL.

namespace NHibernate

{

public enum FetchMode

{

    // Summary:

    //     Default to the setting configured in the mapping file.

    Default=0,

    Lazy=1,

    //

    // Summary:

    //     Fetch eagerly, using a separate select. Equivalent to fetch="select" (and

    //     outer-join="false")

    Select=1,

    //

    // Summary:

    //     Fetch using an outer join. Equivalent to fetch="join" (and outer-join="true")

    Join=2,

    Eager=2,

}

}

Anthony Dewhirst
04/11/2009 07:20 PM by
Anthony Dewhirst

Hi Ayende,

I am finding an issue with the fetch mode set to Join. I have two child collections in my parent class and fetch set to join on both my child classes in the mapping. If I create 2 children and save the mapping, when I reload, I have 4 in each collection. I understand that you will get 4 rows from the join, but I would have thought that NHiberante would have worked out the duplicate identities.

Parent Mapping: (The Children 1 and 2 just contains an Id)

<hibernate-mapping
<class
<id
<generator

<bag

<one-to-many

<bag

<one-to-many

The parent class:

using System.Collections.Generic;

namespace Domain

{

public class Parent : AbstractDomainObject

{

    #region Core Properties


    public virtual IList

<child1 Children1 { get; set; }

    public virtual IList

<child2 Children2 { get; set; }

    #endregion


    #region Constructors


    public Parent()

    {

        Children1 = new List

<child1();

        Children2 = new List

<child2();

    }


    #endregion

}

}

the program:

    static void Main(string[] args)

    {

        Configuration cfg = new Configuration();

        cfg.Configure();


        new SchemaExport(cfg).Drop(false, true);

        new SchemaExport(cfg).Create(false, true);


        ISessionFactory sessionFactory = cfg.BuildSessionFactory();


        using (var session = sessionFactory.OpenSession())

        using (var tx = session.BeginTransaction())

        {

            var parent = new Parent();

            parent.Children1.Add(new Child1());

            parent.Children1.Add(new Child1());


            parent.Children2.Add(new Child2());

            parent.Children2.Add(new Child2());


            session.Save(parent);


            tx.Commit();

        }


        using (var session = sessionFactory.OpenSession())

        using (var tx = session.BeginTransaction())

        {

            var parents = session.CreateCriteria(typeof (Parent)).List

<parent();

            Console.WriteLine("There are {0} children1", parents[0].Children1.Count);

            Console.WriteLine("There are {0} children2", parents[0].Children2.Count);

        }


        Console.ReadKey();

    }

I get that there are 4 children in each collection.

Am I using the wrong collection type or is this a known feature/bug in nhibernate?

Anthony Dewhirst
04/11/2009 07:23 PM by
Anthony Dewhirst

This time with the mapping....I hope

<id name="Id">

  <generator class="native"/>

</id>


<bag name="Children1" cascade="all-delete-orphan" lazy="false" generic="true" fetch="join">

  <key column="ParentId"></key>

  <one-to-many class="Child1" />

</bag>


<bag name="Children2" cascade="all-delete-orphan" lazy="false" generic="true" fetch="join">

  <key column="ParentId"></key>

  <one-to-many class="Child2" />

</bag>

Ayende Rahien
04/21/2009 07:28 AM by
Ayende Rahien

LukeB,

Please post this again in the mailing list, I'll look at it there.

Ayende Rahien
04/21/2009 07:30 AM by
Ayende Rahien

Anthony

That is the expected behavior in this scenario if you are not using a set.

That is because NHibernate will use values from the database, and it has no way of knowing what you are actually intending to do with them.

Daniel Fernandes
04/22/2009 02:47 PM by
Daniel Fernandes

@Ayende

Are there performance issues in having to use @property-ref for those kind of mappings ?

I'm dealing on a daily basis with a (horrible) legacy database and today I had this kind of association:

CATEGORYA one-to-many CATEGORYB and then

CATEGORYB many-to-one CATEGORYB_DATA

CATEGORY_A and B are using single table inheritance using a formula based discriminator.

The association between CATEGORYB and CATEGORYB_DATA is via @property-ref and it's set to use JOIN FETCHING.

If I load up a CATEGORYB object, NHibernate will correctly issue a LEFT OUTER JOIN and will retrieve all the necessary attributes to rehydrate CATEGORYB_DATA objects but then somehow I see further SELECT statements issued for each one of the CATEGORYBDATA that should have already been loaded using the JOIN FETCHING, those SELECT statements are constrained on the value of the @property-ref set on the many to one association.

Would using the @property-ref mess something in the identity map making NHibernate to reload again those same objects ?

I sometimes wonder if NHibernate is the best tool for totally screwed legacy databases....

Daniel

Ayende Rahien
04/22/2009 02:58 PM by
Ayende Rahien

Daniel,

Those type of questions are best served in the NH Users mailing list

k03123
05/17/2009 11:48 PM by
k03123

@Ayende,

with fetch="select" option, it issues seperate select command for each of the many-to-one association if it exists even though i am not using the many-to-one part. is that the intended behaviour?

i thought with fetch="select" option, separate select command would be issued only when many-to-one property is selected.

i am using nhiberntate 2.1.

cheers

Ayende Rahien
05/19/2009 08:19 AM by
Ayende Rahien

k03123,

fetch and lazy are two separate aspects

k03123
05/20/2009 04:12 AM by
k03123

does that mean lazy-loading is not available for many-to-one?

thanks ayende

Ayende Rahien
05/20/2009 06:59 AM by
Ayende Rahien

many-to-one support lazy loading

k03123
05/20/2009 08:13 AM by
k03123

my apologies ayende,

lazy="false" in my many-to-one class. no wonder i wasn't gettting expected results.

thanks

Comments have been closed on this topic.