Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fluent NHibernate One-To-Many Mapping

I have the following 2 classes:

Advert

public virtual int Id { get; set;
public virtual IList<AdvertImage> AdvertImages { get; set; }

AdvertImage

public virtual int Id { get; set; }
public virtual string Filename { get; set;
public virtual Advert Advert { get; set; }

In the DB, my AdvertImages table has the FK 'AdvertId' which relates to the Adverts table which has the PK of 'Id'.

This is a One-To-Many mapping, in that one advert can have many images.

My Fluent NHibernate mappings (edited for brevity) are:

AdvertMap

Id(x => x.Id)
  .GeneratedBy.Identity();
...
HasMany(x => x.AdvertImages)
  .KeyColumn("AdvertId")
  .Inverse();
...
Table("Adverts");

AdvertImageMap

Id(x => x.Id)
  .GeneratedBy.Identity();
...
References(x => x.Advert)
  .Column("AdvertId");
...
Table("AdvertImages");

I am creating a new instance of Advert in code, then populating the AdvertImages property (of Advert) with a List<AdvertImage>.

When I go to persist my Advert object to the DB, I would like the AdvertImages to be inserted into their AdvertImages table, but due to the relationship between the 2 tables, I need the Advert insertion to happen first, so as the PK Id is generated, which can then be inserted in the AdvertImages table. (When I create my list of AdvertImage, I am populating the Filename property, but obviously don't have the new AdvertId at that stage, so want that to be populated when the advert is persisted to the DB).

I have tried experimenting with different Inverse() and Cascade settings but haven't succeeded yet. Can anyone help please?

like image 235
marcusstarnes Avatar asked Jun 01 '12 16:06

marcusstarnes


3 Answers

You need to change your Advert mapping to cascade:

Id(x => x.Id)
  .GeneratedBy.Identity();

HasMany(x => x.AdvertImages)
  .KeyColumn("AdvertId")
  .Inverse()
  .Cascade.AllDeleteOrphan();

Table("Adverts");

You should then be able to do something like this to persist an Advert object and it's children AdvertImage.

Advert newAdvert = new Advert();
AdvertImage newImage = new AdvertImage();
newImage.Advert = newAdvert;
newAdvert.AdvertImages.Add(newImage);

using(NHibernate.ISession session = SessionFactory.GetCurrentSession())
{
    using (NHibernate.ITransaction tran = session.BeginTransaction())
    {
        session.Save(newAdvert);
        tran.Commit();
    }
}

My entities usually contain Add and Remove methods for bidirectional one to many relationships like this:

public class Advert
{
    public virtual IList<AdvertImage> AdvertImages { get; set; }

    public virtual void AddImage(AdvertImage newImage)
    {
        newImage.Advert = this;
        AdvertImages.Add(newImage);
    }
}
like image 158
Cole W Avatar answered Sep 18 '22 14:09

Cole W


I had the same issue. I spent a while trying all different types of mapping. I then discovered my mappings were fine and it was actually that I needed to wrap my session in a transaction and use the Commit() method after the session.SaveOrUpdate() method.

using(var session = sessionFactory.OpenSession()) 
using(var tx = session.BeginTransaction()) 
{ 
// execute code that uses the session 
tx.Commit(); 
}
like image 39
Arnold.Krumins Avatar answered Sep 18 '22 14:09

Arnold.Krumins


What works for me usually is to set the foreign key column to allow nulls in DB - that would be your AdvertId column, but I'm not sure if that would work in your case, since you are using identity. What NHibernate does is INSERT all with one query and then updates child table foreign key column to the correct id of the parent table. Maybe it would work in your case also.

Here's some similar question that might help: Cascade insert on one-to-many with fluent NHibernate

like image 40
Miroslav Popovic Avatar answered Sep 21 '22 14:09

Miroslav Popovic