I'm trying to get NHibernate to use the many side of a collection to manage a bidirectional association to model a zero-to-one relationship.
Parent Class and Map:
public class Parent
{
private ICollection<Child> children;
public Parent()
{
this.children = new HashedSet<Child>();
}
public virtual Guid Id { get; protected internal set; }
public virtual Child Child
{
get { return children.FirstOrDefault(); }
set
{
{
this.children.Clear();
if (value != null)
{
this.children.Add(value);
}
}
}
}
}
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
this.Id(x => x.Id)
.GeneratedBy.GuidComb();
this.HasMany<Child>(Reveal.Member<Parent>("children"))
.Access.Field()
.Cascade.All()
.Not.Inverse()
.AsSet();
}
}
Child Class and Map:
public class Child
{
public virtual Guid Id { get; protected internal set; }
public virtual Parent Parent { get; set; }
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
this.Id(x => x.Id)
.GeneratedBy.GuidComb();
this.References(x => x.Parent)
.Not.Nullable()
.Cascade.All();
}
}
The following code produces two inserts and an update:
var parent = new Parent();
var child = new Child();
parent.Child = child;
child.Parent = parent;
session.Save(parent);
session.Flush();
Notice the essentially duplicate SQL for the second insert and the following update:
exec sp_executesql N'INSERT INTO [Parent] (Id) VALUES (@p0)',N'@p0 uniqueidentifier',@p0='AA5A146E-E3F5-4373-B7A8-9EF301171401'
go
exec sp_executesql N'INSERT INTO [Child] (Parent_id, Id) VALUES (@p0, @p1)',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='AA5A146E-E3F5-4373-B7A8-9EF301171401',@p1='B78C4461-A217-47FC-BE02-9EF30117140A'
go
exec sp_executesql N'UPDATE [Child] SET Parent_id = @p0 WHERE Id = @p1',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='AA5A146E-E3F5-4373-B7A8-9EF301171401',@p1='B78C4461-A217-47FC-BE02-9EF30117140A'
go
While this code produces the infamous not-null property references a null or transient value inverse
:
var parent = new Parent();
var child = new Child();
parent.Child = child;
//child.Parent = parent;
session.Save(parent);
session.Flush();
I've found numerous posts about this, but have yet to find a definitive guide on how to do zero-to-one, with inverse=false
on the one
side.
I've tried the one-to-many/one-to-one method mentioned here.
As well, I've found several open issues on NHibernate about (not)nullable Foreign Keys: NH-941, NH-1050, etc..
What am I doing wrong?
Edit 2011-05-30
So, my temporary solution is to go for the standard inverse=true
setting on the many side, and do some magic in the setter of the Parent:
public virtual Child Child
{
get { return children.FirstOrDefault(); }
set
{
{
this.children.Clear();
if (value != null)
{
value.Parent = this;
this.children.Add(value);
}
}
}
}
But I'm still baffled by the inverse=false
behavior, which should be the equivalent of inverse=true
on the many-to-one side (interestingly, FluentNhibernate doesn't allow for the ManyToOnePart to set inverse=true
like this article recommends).
When to use inverse="true|false"
The inverse
attribute is used to help NHibernate know which side of a relationship should be used to persist the relationship. The non-inverse side (please note the double negative) is the side that will be persisted. If neither side is inverse, then the relationship will be persisted twice, like with the INSERT
followed immediately by an UPDATE
example you provided above. If both sides are inverse, then the relationship won't be persisted at all, so it's important to set inverse correctly.
I like to think about inverse
in the following way. I don't know whether this is officially the way it works or not, but it helps my world make sense:
many-to-one
many-to-one
relationships are always inverse="false"
. They are always used to persist the relationship to the database. Since they are always inverse="false"
, there's no need to specify it, so NHibernate (and hence Fluent NHibernate) doesn't provide an option for it.
(I have run across only one situation where I wish I could specify inverse="true"
on a many-to-one
. If you have a one-to-many
list
on one side and a many-to-one
on the other side, you should be able to let the list
control the relationship so that NHibernate can take care of setting the index
values for you. As it currently stands, you have to add a property to the child class and manage the index
values yourself.)
one-to-one
one-to-one
relationships are always inverse="true"
. They never exist without either an id
or many-to-one
on the other side of the relationship which will take care of persisting the relationship. Since the inverse
value is always the same, there's no need to specify it, so specifying it is not supported.
Collections
Collections like bag
, list
, set
, etc. may or may not be a part of a bi-directional relationship. If they exist on their own (perhaps a bag of string elements), then they need to be inverse="false"
(which is the default) because no one else will be responsible for persisting the relationship. If they exist in conjunction with another relationship, however (like your traditional one-to-many/many-to-one) they should be specified as inverse="true"
.
With many-to-many
collections where you have a collection on either side of the relationship, mark one of them as inverse="true"
and leave the other one as the default inverse="false"
. Again, the point is that one side of the relationship must be non-inverse. Which side should you pick? If we take a many-to-many relationship between Users and Roles for example, you probably have lots of Users and a few Roles. In my opinion, you should map Role.Users
as inverse="true"
and let User.Roles
control the relationship since it's a smaller set of data to work with and it's probably the collection you care more about anyway.
(In fact, I would be hesitant to include Role.Users
in the model at all. Suppose a "Customer" role has 100,000 users. Then customerRole.Users
is an unusable lazy-loading bomb waiting to explode.)
...back to your question...
Since it doesn't really matter which side of the relationship is inverse, just so long as one side is non-inverse, then you should make the one-to-one
side the inverse side since that's the way NHibernate wants to do it. Don't fight the tool over stuff that doesn't matter. In the mappings you provided, essentially both sides of the relationship have been marked non-inverse, which caused the relationship to be persisted twice. The following mappings should work better for you:
public class Parent
{
public virtual Guid Id { get; set; }
public virtual Child Child { get; set; }
}
public class ParentClassMap : ClassMap<Parent>
{
public ParentClassMap()
{
Id(x => x.Id);
HasOne(x => x.Child)
.PropertyRef(x => x.Parent)
.Cascade.All();
}
}
public class Child
{
public virtual Guid Id { get; set; }
public virtual Parent Parent { get; set; }
}
public class ChildClassMap : ClassMap<Child>
{
public ChildClassMap()
{
Id(x => x.Id);
References(x => x.Parent)
.Not.Nullable()
.Unique()
.Cascade.SaveUpdate();
}
}
... which results in the following SQL from your test insertion code:
exec sp_executesql N'INSERT INTO [Parent] (Id) VALUES (@p0)',N'@p0 uniqueidentifier',@p0='925237BE-558B-4985-BDA2-9F36000797F5'
exec sp_executesql N'INSERT INTO [Child] (Parent_id, Id) VALUES (@p0, @p1)',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='925237BE-558B-4985-BDA2-9F36000797F5',@p1='BE6D931A-8A05-4662-B5CD-9F36000797FF'
No update query!