NHibernate Mapping - <one-to-one/>
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:
And the database model:
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:
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:
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:
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
Hi.
Is it possible set new Employee on existing Person?
Thanks for reply.
Great explanation, the constrained="true" / foreign-key="none" part is really helpful.
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.
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
Your sample doesnt work if I use guid generator. I think this sample is not correct way using one-to-one mapping.
Nik,
what is the error you are getting?
In database person with id=8, employee with id=3(and in person column 8).
Code:
using (var session = sessionFactory.OpenSession())
<person(8).Employee;
generates:
SELECT person0_.Id as Id1_0_,
FROM People person0_
WHERE person0_.Id = 8 /* @p0 */
SELECT employee0_.Id as Id0_1_,
FROM Employee employee0_
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 Id1_2_,
FROM People person0_
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
£class name="Person" table="People"£
£/class£
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.
In your sample Person.Id, Employee.Id and Employee.Person, it's not real case, this what i want to say.
In your sample Person.Id, Employee.Id and Employee.Person equals, it's not real case, this what i want to say.
How can I to create a mapping when a Person can be Employee and/or Customer and/or Author?
Take a look at the "any" post
I have problem about this on Eager Load. my mapping file:
<class
<id
<generator
It generate 2 sql statements:
NHibernate: SELECT person0_.ID as ID4_1_, person0_.Name as Name4_1_, employee1_.ID as ID3_0_, employee1_.Name as Name3_0_, employee1_.PeopleID as PeopleID3_0_ 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 ID3_0_, employee0_.Name as Name3_0_, employee0_.PeopleID as PeopleID3_0_ 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?
Stevey,
Take a look at constrained="true"
Comment preview