There are lots of details on how to map list of Items with NHibernate.
This an in-depth look at what all of the options and details.

Let us have this simple example:

The simplest way to map this into domain model, is to have these two classes:
public class Person
{
    public int Id {get; set; }
    public string FirstName { get; set; }
    public string SecondName { get; set; }
}

public class PersonPhone
{
    public int Id { get; set; }
    public string PhoneNumber { get; set; }
    public Person Owner { get; set; }
}
and to model this with fluent nhibernate

public class PersonPhoneMapping : ClassMap<PersonPhone>
{
    public PersonPhoneMapping() : base()
    {
        Id(x => x.Id);
        Map(x => x.PhoneNumber);
        References(x => x.Owner, "PersonId");
    }
}

That simple association, will just mirror the database model.
If we want to have the relation from the other side, then we can write this mapping

public class Person
{
    //.... keep all properties from above
    public IList<personphone> Phones {get; set;}
}
public class PersonMapping : ClassMap<Person>
{
    Id(x => x.Id);
    Map(x => x.FirstName);
    Map(x => x.SecondName);
    HasMany<PhoneContact>(x =>x.Phones).KeyColumn("PersonId");
}

The above mapping will fail with our database model, when we delete phones from the phone list of a person.
The reason is, NHibernate default behavior when delete an item from the many side, of one-to-many relation, is to delete the association, by simple putting null into the column personId.
So to delete the row, we have this mapping


public class PersonMapping : ClassMap<Person>
{
    Id(x => x.Id);
    Map(x => x.FirstName);
    Map(x => x.SecondName);
    HasMany<PhoneContact>(x =>x.Phones).KeyColumn("PersonId").Cascade.AllDeleteOrphan();
}

Why NHibernate by default don't delete the row in database when we delete the child item from the domain model?

Because NHibernate assume that it could be in another relationship, so it won't delete it.

When we specify "Cascade.AllDeleteOrphan()" then we tell NHibernate that this child item exists in the scope of the parent item only.

And this point is important, which is the life cycle of the child item.

If the child item exists beyond the scope of the parent item, then don't use that option, otherwise use that option, so when you delete the child item from the list of the parent, and delete the item, then the item will be deleted from the database.



Making the association Bidirectional
It is the domain model responsibility to maintain the bidirectional relation between the classes.
But from NHibernate side, there is a little bit gacha that we should describe.


public class PersonMapping : ClassMap<Person>
{
    Id(x => x.Id);
    Map(x => x.FirstName);
    Map(x => x.SecondName);
    HasMany<PhoneContact>(x =>x.Phones).KeyColumn("PersonId").Inverse().Cascade.AllDeleteOrphan();
}

the gatcha here is the use of "Inverse" in the mapping.

Without this inverse, and when there is a bidirectional mapping, NHibernate will try to do two updates on the column PersonId, one from the Person table side, and one from PersonPhone side.

Inverse , will tell the parent side, in this case the Person, that it is the child side, PersonPhone, will handle the relation.

And from the domain model side, we should maintain this bidirectional association:

public class Person
{
    public IList<personphone> Phones {get; set; }
    public void AddPhone(PersonPhone phone)
    {
        this.Phones.Add(phone);
        phone.Owner = this;
    }
    
    //.....
}
What about persistence?
By adding "Inverse" to the mapping, then we are totally decoupling the changes on child item "PersonPhone" from its parent "Person".

Which means, even we added a phone to a person, with the public method "AddPhone", we are not going to save it into the database, because we set the Inverse option.

But it make sense to make NHibernate do the adding of phone for us, when we added to the Person.

To do that , use this mapping


public class PersonMapping : ClassMap<Person>
{
    Id(x => x.Id);
    Map(x => x.FirstName);
    Map(x => x.SecondName);
    HasMany<PhoneContact>(x =>x.Phones).KeyColumn("PersonId").Inverse().Cascade.SaveUpdate();
}
Then what cascade I should choose?
Above we saw two mapping options:

  • Cascade.SaveUpdate()
  • Cascade.AllDeleteOrphans()

How to choose between these two?

If the child item exists only in the scope of the parent item, then use the second one.

So, if you delete the item from the parent, and save the parent, it will delete the row from the database.

and as well , when you add an item to the list, and save the parent , then it will add a new child row into the database.

While the first option (SaveUpdate) will only do the second option, where it adds the item to the database, when we add it to the parent, without delete it from the database when we remove it from the list.