Ayende @ Rahien

Refunds available at head office

NHibernate Mapping -

In the database world, we have three kind of associations: 1:m, m:1, m:n.

However, occasionally we want to have a one to one relationship. We could simulate it easily enough on the database side using two many to one relations, but that would require us to add the association column to both tables, and things gets… tricky when it comes the time to insert or update to the database, because of the cycle that this creates.

NHibernate solves the problem by introducing a one-to-one mapping association, which allow you to define the two relationships based on a single column in the database, which controls the two way association.

<one-to-one
        name="PropertyName"                                (1)
        class="ClassName"                                  (2)
        cascade="all|none|save-update|delete"              (3)
        constrained="true|false"                           (4)
        fetch="join|select"                                (5)
        property-ref="PropertyNameFromAssociatedClass"     (6)
        access="field|property|nosetter|ClassName"         (7)
/>

1, 2, 3, 6, 7 were all discussed elsewhere, so I’ll skip them and move directly to showing how this can be used.

We have the follow object model:

image

And the database model:

image

Note that while in the object model we have a bidirectional mapping, in the database we have only a single reference on the employees table. In the relational model, all associations are naturally bidirectional, but that is not true on the object model. In order to bridge this inconsistency, we map them as:

<class name="Employee"
		table="Employees">

	<id name="Id">
		<generator class="native"/>
	</id>
	
	<property name="Role"/>

	<many-to-one name="Person"
		unique="true"
		column="Person"/>
</class>

<class name="Person"
		table="People">

	<id name="Id">
		<generator class="native"/>
	</id>
	<property name="Name" />
	<one-to-one name="Employee"
			class="Employee"/>
</class>

We have a unique many-to-one association from Employee to Person, but a one to one from Person to Employee. This will reuse the many-to-one association defined in the Employee mapping.

Let see how this works for saving and loading the data:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
	var person = new Person
	{
		Name = "test",
	};
	var employee = new Employee
	{
		Person = person,
		Role = "Manager"
	};
	person.Employee = employee;
session.Save(person);
session.Save(employee); tx.Commit(); } // person to employee using (var session = sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { var employee = session.Get<Person>(1).Employee; Console.WriteLine(employee.Role); tx.Commit(); } // employee to person using (var session = sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { var person = session.Get<Employee>(1).Person; Console.WriteLine(person.Name); tx.Commit(); }

And the SQL that would be generated would be:

image

This is quite interesting. We can see that we insert the entities as we expect, but when we pull a person out, we do a join to the employee, to get the one-to-one association. For that matter, even in the second scenario, we do a join to get the associated employee.

The reason that we have to do it is quite interesting as well. NHibernate makes some guarantees about the way the object model and the database model map to one another. And one of those guarantees is that if there is no association in the database, we will get back a null in the object model.

Generally, this works very well, since we can tell whatever an association exists or not using the value in the table (for many-to-one associations). But for one-to-one association, if we want to keep this guarantee, we have to check the associated table to verify if we need to have a null or a proxy there. That is somewhat annoying, but we can get around that by specifying constrained=”true”. This tell NHibernate that in this case, whenever there is a Person, there must also be a matching Employee value. We can specify it like this:

<one-to-one name="Employee" 
	constrained="true"
	foreign-key="none"
	class="Employee"/>

Something else to note is that we must specify this with foreign-key=”none”, because otherwise NHibernate’s Schema Export feature would create two foreign keys for us, which would create a circular reference that wouldn’t allow us to insert anything into the database.

When setting this, we can see that there is a dramatic change in NHibernate’s behavior:

image

Instead of generating joins, NHibernate now uses standard selects to get the data. And we don’t have to pre-populate the information on loading the entity, we can delay that as we usually do with NHibernate.

And the last thing that we will explore for <one-to-one/> is the fetch attribute. It defaults to select, so we have already seen how that works, but when we set fetch=”join”, we get an interesting flashback. Well, almost:

image

Again, we use a join to get the value upfront, but since we are now using constrained=”true”, we can use an inner join instead of a left outer join, which is more efficient in most cases.

Comments

Nik
04/19/2009 08:18 AM by
Nik

Hi.

Is it possible set new Employee on existing Person?

Thanks for reply.

Steve Willcock
04/19/2009 11:40 AM by
Steve Willcock

Great explanation, the constrained="true" / foreign-key="none" part is really helpful.

Chris
04/19/2009 03:38 PM by
Chris

How do you emulate a one to many relationship in ActiveRecord, where you need to filter the key based on a column? For example my MailAccount has lots of Contacts, but I only want to those Contacts of a particular type in the relationship? From what I can see it's not possible in AR, but is in NH.

Ayende Rahien
04/20/2009 08:25 AM by
Ayende Rahien

Nik,

Yes, it is possible. Take care to ensure that it is always set to A person, if you are using constrained, though.

Chris,

Use the Where property

Nik
04/21/2009 08:16 PM by
Nik

Your sample doesnt work if I use guid generator. I think this sample is not correct way using one-to-one mapping.

Ayende Rahien
04/22/2009 05:27 AM by
Ayende Rahien

Nik,

what is the error you are getting?

Nik
04/22/2009 11:04 AM by
Nik

In database person with id=8, employee with id=3(and in person column 8).

Code:

using (var session = sessionFactory.OpenSession())

        using (var tx = session.BeginTransaction())

        {

            var employee = session.Get

<person(8).Employee;

            Console.WriteLine(employee.Role);

            tx.Commit();

        }

generates:

SELECT person0.Id as Id10_,

   person0_.Name as Name1_0_

FROM People person0_

WHERE person0_.Id = 8 /* @p0 */

SELECT employee0.Id as Id01_,

   employee0_.Role   as Role0_1_,

   employee0_.Person as Person0_1_,

   person1_.Id       as Id1_0_,

   person1_.Name     as Name1_0_

FROM Employee employee0_

   left outer join People person1_

     on employee0_.Person = person1_.Id

WHERE employee0_.Id = 8 /* @p0 */

Exception was thrown:

No row with the given identifier exists[OneToOneEntities.Employee#8]

If I'll set fetch="join" on one-to-one association, NH will generate this query:

SELECT person0.Id as Id12_,

   person0_.Name     as Name1_2_,

   employee1_.Id     as Id0_0_,

   employee1_.Role   as Role0_0_,

   employee1_.Person as Person0_0_,

   person2_.Id       as Id1_1_,

   person2_.Name     as Name1_1_

FROM People person0_

   inner join Employee employee1_

     on person0_.Id = employee1_.Id -- Yeah!!!!!!!!!!!!!!!!

   left outer join People person2_

     on employee1_.Person = person2_.Id

WHERE person0_.Id = 8 /* @p0 */

and {"Object reference not set to an instance of an object."} Exception will be thrown.

one-to-one association bind primary keys:

<class
<id
<generator
<param Employee

<property
<one-to-one

Nik
04/22/2009 11:06 AM by
Nik

£class name="Person" table="People"£

£id name="Id"£

    £generator class="foreign"£

      £param name="property"£ Employee £/param£

   £/generator£

£/id£

£property name="Name" /£

£one-to-one name="Employee" class="Employee" constrained="true"/£

£/class£

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

Nik,

You set it up in such a way that the Id or Person and the Employee must have the same value.

That is what foreign means.

Nik
04/22/2009 04:15 PM by
Nik

In your sample Person.Id, Employee.Id and Employee.Person, it's not real case, this what i want to say.

Nik
04/22/2009 04:18 PM by
Nik

In your sample Person.Id, Employee.Id and Employee.Person equals, it's not real case, this what i want to say.

Luiz Sergio
04/23/2009 06:03 PM by
Luiz Sergio

How can I to create a mapping when a Person can be Employee and/or Customer and/or Author?

Ayende Rahien
04/23/2009 06:05 PM by
Ayende Rahien

Take a look at the "any" post

stevey
04/27/2009 09:13 PM by
stevey

I have problem about this on Eager Load. my mapping file:

<class
<id
<generator

<property
<one-to-one
property-ref="Person"

            class="MyDomain.Test.Employee, MyDomain" 

            cascade="all" fetch="join" lazy="false"/>

<class
<id
<generator

<property
<many-to-one
column="PeopleID"

             class="MyDomain.Test.Person, MyDomain" unique="true" 

             />

I have one person and one employee in my DB and I run this simple test:

        var person  = repository.FindByID(new Guid  (  "30ad691d-84c0-4175-815f-9bf800f86766"));

        Assert.That(person,Is.Not.Null);

        Assert.That(person.Employee, Is.Not.Null);

It generate 2 sql statements:

NHibernate: SELECT person0.ID as ID41, person0.Name as Name41, employee1.ID as ID30, employee1.Name as Name30, employee1.PeopleID as PeopleID30 FROM People person0 left outer join Employees employee1_ on person0.ID=employee1.PeopleID WHERE person0_.ID=@p0; @p0 = '30ad691d-84c0-4175-815f-9bf800f86766'

NHibernate: SELECT employee0.ID as ID30, employee0.Name as Name30, employee0.PeopleID as PeopleID30_ FROM Employees employee0_ WHERE employee0_.PeopleID=@p0; @p0 = '30ad691d-84c0-4175-815f-9bf800f86766'

Which I assume it should only generate one.

Can I make it only generate one?

Ayende Rahien
04/28/2009 04:33 AM by
Ayende Rahien

Stevey,

Take a look at constrained="true"

Comments have been closed on this topic.