I'm trying to do a one-to-many mapping, from a Master object which
contains a .Details collection with a set of Detail objects. In my
domain model, this only needs to be "uni-directional": that is, Detail
does NOT have a .Master property that refers back to its master. I
never need to have a Detail object by itself, nor will I ever load a
single Detail object -- it will always be accessed from Master first.
For a concrete example, consider User and UserPreferences. I don't
need to access a UserPreference by itself, I will always access it in
the context of a User (via User.Preferences). Contrast this to a
different type of one-to-many relationship, like between User and
BlogPost. A BlogPost is interesting by itself, and will often be the
entity the application is loading (the main page of a blog, for
example) - and so having a bi-directional relationship (so I have both
BlogPost.User and User.BlogPosts) makes sense.
Here's what the classes look like for my example:
public class Detail {
public virtual Guid DetailId { get; set; }
public virtual string Name { get; set; }
}
public class Master {
public virtual Guid MasterId { get; set; }
public virtual string Name { get; set; }
public virtual IList<Detail> Details { get; set; }
}
The Master database table is:
masterId uniqueidentifier NOT NULL
name nvarchar(max) NULL
and Detail is:
DetailId uniqueidentifier NOT NULL
MasterId uniqueidentifier NOT NULL
name nvarchar(max) NULL
foreign key (masterId) references [Master]
So what I'd like to be able to do is this:
Master mast = new Master {
MasterId = new Guid(),
Name = "test",
Details = new List {
new Detail {
DetailId = new Guid(),
Name = "Test1"
},
new Detail {
DetailId = new Guid(),
Name = "Test1"
}
}
};
using (transaction == Session.BeginTransaction) {
Session.Save(mast);
transaction.Commit();
}
The problem is, the Save will fail -- because NH tries to:
* Insert the Master object
* Insert the Detail object, but with MasterId = NULL
* Update the Detail object with the correct MasterId.
It fails on the second step because the database doesn't allow
MasterId of NULL.
For reference, here's the mapping (fluent) I'm using:
public class MasterMap : ClassMap<Master> {
public MasterMap() {
Id(x => x.MasterId).GeneratedBy.Guid();
Map(x => x.Name);
}
}
public class DetailMap : ClassMap<Detail> {
public DetailMap() {
Id(x => x.Id).GeneratedBy.Guid();
Map(x => x.Name);
HasMany(x => x.Details).Not.KeyNullable.Cascade.All();
}
}
I can fix this by adding a reference backwards:
Add References(x => x.Master).Column("masterId") to the DetailMap;
add a Detail.Master property; set the Master.Details as a .Inverse()
relation; and set the Detail.Master property whenever adding a Detail
to a Master.Details collection.
Less than ideal, because now I need to do a bunch of "overhead work"
in my business code (setting Detail.Master), not to mention it makes
it prone to error if I don't do that, or somehow it gets set to a
different ID than the collection it actually belongs to, etc.
(also note, in case anyone with a similar problem is reading this:
without .Inverse(), it will only do the INSERT/UDPATE
behaviour. .Invese() is needed to make it set MasterId in the initial
INSERT.
One way to at least abstract this from my client code is to have my
own DetailCollection type, and override the .Add method so it sets
Detail.Master automatically.. but this won't work if I load an
existing Master object, as NHibernate will set it to be a
PersistentGenericBag<Detail> instead of my own DetailCollection type.
Is it a bug that NH doesn't support calling Save() as I did in my
first example? Is it simply a side-effect of supporting different
types of ID generation on different databases, and if so, can this be
fixed? Is it an intended design decision ,and if so, what's the
rationale behind it? I know this is documented at
http://www.nhforge.org/doc/nh/en/index.html#collections-onetomany (it
says you need to use a bi-directional relationship), but it doesn't
explain the "why" behind this.
On the flip side, if there is a legitimate reason behind this and/or
it can't be fixed, what is the proper pattern I should be using? Is
there something nicer than using a bi-directional relation and
requiring all my client code to have to do:
detail.Master = mast;
mast.Details.Add(detail);
Thanks
--
You received this message because you are subscribed to the Google Groups
"nhusers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/nhusers?hl=en.