NHibernate one-to-one
I should start by saying that <one-to-one> is not a recommended approach, but the question came up in the Castle mailing list, and I set to investigate. There are scenarios where this is the only choice, but in general, prefer to avoid it. Here are our entities:
[ActiveRecord("Users")]
publicclassUser : ActiveRecordBase<User>
{
privateBlog blog;
privateint id;
privatestring name;
[PrimaryKey]
publicvirtualint Id
{
get { return id; }
set { id = value; }
}
[Property]
public virtual string Name
{
get { return name; }
set { name = value; }
}
[OneToOne]
publicvirtualBlog Blog
{
get { return blog; }
set { blog = value; }
}
}
[ActiveRecord]
public class Blog : ActiveRecordBase<Blog>
{
private int id;
private User user;
private string name;
[PrimaryKey]
public virtual int Id
{
get { return id; }
set { id = value; }
}
[BelongsTo("`User`")]
public virtual User User
{
get { return user; }
set { user = value; }
}
[Property]
public virtual string Name
{
get { return name; }
set { name = value; }
}
}
Now, let us see what happens when I try to run this code:
using(new SessionScope())
{
User user = User.Find(u.Id);
Console.WriteLine(user.Name);
}
For this, NHibernate will generate the following query:
declare @p0 int;
set @p0 = '1';
SELECT user0_.Id as Id1_2_, blog1_.Id as Id0_0_,
blog1_.Name as Name0_0_, blog1_.[User] as User3_0_0_,
user2_.Id as Id1_1_
FROM Users user0_ left outer join
Blog blog1_ on user0_.Id=blog1_.Id
left outer join Users user2_
on blog1_.[User]=user2_.Id WHERE user0_.Id=@p0;
The double left outer join had me scratching my head for a while, until I figured out how NHibernate was thinking about it. The first left outer join is to find the related blog, and the second is to find the blog's user. Not reasonable, I agree, but that is the way that one-to-one works.
Now, let us see what happens if we specify that both classes are lazy? Well, now NHibernate generate this SQL:
declare @p0 int;
set @p0 = '1';
SELECT user0_.Id as Id1_1_, user0_.Name as Name1_1_,
blog1_.Id as Id0_0_, blog1_.Name as Name0_0_,
blog1_.[User] as User3_0_0_
FROM Users user0_
left outer join Blog blog1_ on user0_.Id=blog1_.Id
WHERE user0_.Id=@p0;
But wait, didn't we specify that both classes should be lazy? It removed one left outer join, but kept the second one, why is it doing that?
Well, let us give a moment's thought to the way NHibernate see things, shall we? We told it that User has a one to one assoication with Blog, this means that when it loads a User, it has to populate all the properties of the user, but the Blog's column is not kept on the Users table, but on the "Blog" table. What this means is that in order to find the id of the blog entity, NHibernate must query the Blog table as well. At that point, it is more efficent to just grab all the data from the table rather than just the id.
In other words, one-to-one cannot be lazily loaded, which is one of the reasons why it is recommended to use two many-to-one instead.
Comments
One-to-one can be lazily loaded if you promise NHibernate that the other side of the relationship always exists, this is done by specifying constrained="true".
The two outer joins look like a bug to me, but I don't know what ActiveRecord does for [BelongsTo]. If both classes had a <one-to-one> mapping, there should only be one outer join.
[BelongsTo] translate to <many-to-one>
When specifying constrained = true, I get this, better than outer join, but not what I would expect:
NHibernate: SELECT user0_.Id as Id1_1_, user0_.Name as Name1_1_, blog1_.Id as Id0_0_, blog1_.Name as Name0_0_, blog1_.[User] as User3_0_0_ FROM Users user0_ inner join Blog blog1_ on user0_.Id=blog1_.Id WHERE user0_.Id=@p0; @p0 = '1'
For reference, here is the NH mapping that it generates:
<class name="ConsoleApplication1.Blog, ConsoleApplication1" table="Blog" lazy="true">
</class>
<class name="ConsoleApplication1.User, ConsoleApplication1" table="Users" lazy="true">
</class>
And this is of course wrong, since <one-to-one> in general should be matched by a <one-to-one> on the other side since one-to-one means PK to PK association. There are exceptions of course, but your case is not one of them.
Okay, but changing them both to one-to-one (which the User.Blog one being Constrained=true) result in :
NHibernate: SELECT user0_.Id as Id1_1_, user0_.Name as Name1_1_, blog1_.Id as Id0_0_, blog1_.Name as Name0_0_ FROM Users user0_ inner join Blog blog1_ on user0_.Id=blog1_.Id WHERE user0_.Id=@p0; @p0 = '1'
"At that point, it is more efficent to just grab all the data from the table rather than just the id."
I'm not sure I agree with this statement. In fact, if the corresponding table row has many very large text or binary columns, how can it ever be more efficient to fetch the whole row as opposed to just fetching the id?
Dear Captain,
It is fairly rare case, and there are other ways to deal with that.
Comment preview