views:

542

answers:

2

My model contains a class Section which has an ordered list of Statics that are part of this section. Leaving all the other properties out, the implementation of the model looks like this:

public class Section
{
    public virtual int Id { get; private set; }
    public virtual IList<Static> Statics { get; private set; }
}

public class Static
{
    public virtual int Id { get; private set; }
}

In the database, the relationship is implemented as a one-to-many, where the table Static has a foreign key pointing to Section and an integer column Position to store its index position in the list it is part of.

The mapping is done in Fluent NHibernate like this:

public SectionMap()
{
    Id(x => x.Id);
    HasMany(x => x.Statics).Cascade.All().LazyLoad()
            .AsList(x => x.WithColumn("Position"));
}

public StaticMap()
{
    Id(x => x.Id);
    References(x => x.Section);
}

Now I am able to load existing Statics, and I am also able to update the details of those. However, I cannot seem to find a way to add new Statics to a Section, and have this change persisted to the database. I have tried several combinations of:

  • mySection.Statics.Add(myStatic)
  • session.Update(mySection)
  • session.Save(myStatic)

but the closest I have gotten (using the first two statements), is to an SQL exception reading: "Cannot insert the value NULL into column 'Position'". Clearly an INSERT is attempted here, but NHibernate does not seem to automatically append the index position to the SQL statement.

What am I doing wrong? Am I missing something in my mappings? Do I need to expose the Position column as a property and assign a value to it myself?

EDIT: Apparently everything works as expected, if I remove the NOT NULL constraint on the Static.Position column in the database. I guess NHibernate makes the insert and immediatly after updates the row with a Position value.

While this is an anwers to the question, I am not sure if it is the best one. I would prefer the Position column to be not nullable, so I still hope there is some way to make NHibernate provide a value for that column directly in the INSERT statement.

Thus, the question is still open. Any other solutions?

+2  A: 

In the Static's table you have a field called "SectionID" or something similar. Let this field be NULLable: NHibernate first adds the new record, then updates the referenced id.

I have found that in my db and I am surprised too: why the NH doesn't like proper table references at database level?

twk
will observer this also :)
twk
+6  A: 

When using a bidirectional one-to-many relationship in NHibernate one of the ends must be "inverse". Best practice is to set the end with the collection as inverse, since that avoids unnecessary SQL statements and allows the id column to be "not null".

In section 6.4 of the documentation you can find the following note:

Very Important Note: If the column of a association is declared NOT NULL, NHibernate may cause constraint violations when it creates or updates the association. To prevent this problem, you must use a bidirectional association with the many valued end (the set or bag) marked as inverse="true". See the discussion of bidirectional associations later in this chapter.

So, you need to add .Inverse() to your HasMany mapping in SectionMap.

public SectionMap()
{
    Id(x => x.Id);
    HasMany(x => x.Statics)
        .Cascade.All()
        .LazyLoad()
        .Inverse()
        .AsList(x => x.WithColumn("Position"));
}

You would also probably want an Add and Remove method on Section, which sets/resets the reference of the static as well as adding/removing the static to/from its own collection:

public virtual void AddStatic(Static static)
{
    Statics.Add(static);
    static.Section = this;
}


public virtual void RemoveStatic(Static static)
{
    Statics.Remove(static);
    static.Section = null;
}

These methods makes sure the references are kept accurate on both sides of the relationship.

According to section 6.8 of the docs NHibernate does not support bidirectional relationships when using indexed collections:

Please note that NHibernate does not support bidirectional one-to-many associations with an indexed collection (list, map or array) as the "many" end, you have to use a set or bag mapping.

So, if you are still having trouble, consider using a unidirectional relationship instead of a bidirectional, however that might mean that your foreign key column needs to be nullable (according to the note in the beginning of the post). Otherwise you might have to map your collection as a bag or set instead of a list.

Erik Öjebo
Ok, seems reasonable to me. But I will still need to make the `Section.Position` column in the database schema nullable, right?
Jørn Schou-Rode
I'm not sure since you use a List. If you use a bag or set instead of list the column does not need to be nullable. I made a little update to the answer, and according to the docs bidirectional relationships are not supported for list, map or array. So, it seems to be a little tricky using List with a not-null foreign key column.
Erik Öjebo
If I use a bag og a set the column wouldn't even need be there, right?. In this case i need the ordering, and as such a list seems to be the right choice.
Jørn Schou-Rode
Argh - I am getting confused here. In my case, making the `Static.Position` column nullable made it work. I havn't tried marking the `Static.SectionID` field nullable - is that what you are suggesting? Also: which part, `Section` or `Static` would you tell NHibernate to save/update when adding a new `Static`?
Jørn Schou-Rode
Deleted previous comment, I know it works with bag but no idea on list. Bag maps to IList just fine if that is the only reason you are using list. I know if I just add a reference to FNH it defaults to bag. Anyways, with your cascades setup you only have to save the parent. Also, don't worry about Update unless you really need Update support (saving a persisted entity not in the session). The session will otherwise track changes to the collection and insert children on a flush / commit. If you want it to delete children removed from the collection then you'll want AllDeleteOrphans.
eyston
I need to map as a list if the ordering of the "many" items are significant, right? When doing this mapping I need a `Position` column on the "many" items, and to make stuff work I apparently need to make this column nullable, even though it shouldn't need be. Erik's answer has many nice points, but it doesn't offer a solution to my problem. Do I really need to make `Position` nullable? =\
Jørn Schou-Rode