Detail is a value-object (component) not an entity.

On Fri, Dec 17, 2010 at 7:34 PM, gregmac <[email protected]> wrote:

> 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]<nhusers%[email protected]>
> .
> For more options, visit this group at
> http://groups.google.com/group/nhusers?hl=en.
>
>


-- 
Fabio Maulo

-- 
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.

Reply via email to