NHibernate Inverse attribute

By | August 22, 2008

I had to read documentation, articles, many blog entries and go into discussions with colleagues… all to get the root of this ambiguous attribute of NHibernate, it is trickey!
This blog entry is another trial to explain the attribute, but with taking one more detailed step into the explanation.

NHibernate is meant to persist objects as well as to manage their relations to each other, lets take a look at the following example of Parent and Child classes:

public class Parent

    {

        public virtual int Id { get; set; }

        public virtual string Name { get; set; }

        public virtual IList<Child> MyChildren { get; set; }

    }

    public class Child

    {

        public virtual int Id { get; set; }

        public virtual string Name { get; set; }

        public virtual Parent MyParent { get; set; }

and we have corresponding tables to these two classes like the following:

Parent

Child

(note 1: as we are proceeding that there is no Null constraint on the ParentId foreign key in the Child Table)
Finally, we check the interesting part of the mapping files:

  1. Parent:

    <bag name=MyChildren table=Child cascade=all>

          <key column=ParentId/>

          <one-to-many class=Child/>

  2. Child:

    <many-to-one name=MyParent class=Parent >

          <column name=ParentId/>

        </many-to-one>

(note 2: all what the cascade=”all” attribute in the MyChildren bag does is that when ever you save the Parent, all the Child objects in the MyChildren collection are forced to be saved as well; and we are only talking about the Save operation; not interfering or changing any of the values of the Child properties)

Now if we execute the following code:

Parent par = Session.Get<Parent>(8);

            Child ch = new Child();

            ch.Name = “Emad”;

            par.MyChildren.Add(ch);

            Session.Save(par);

As you may expect, both objects “par” and “ch” will be saved due to note 2 we mentioned above, but the surprise is that when you check the values in the database, you will see that the ParentId field was set as well although we didn’t set it explecitly in the code!

dbValues

The reason is that there is a hidden attribute called “Inverse” in the bag part of the Parent map file whose default value is “false”; when this attribute is set to false like the default value, then the Parent says: “Oh, so it is my responsibility to maintain the relationship with my child objects, ok then when ever a child is added to my collection, when I am saved…I will perform an update SQL statement to their foreign key to point at me”

So when the Save is called, the “par” object is saved, and the “ch” is inserted because calling Session.Save(object) when the object is new it will be inserted.  And after all that happens, an explicit update SQL statement will be executed to update all the child objects to set the foreign key ParentId to the par object Id.

This goes all fine in our case, only due to note 1 (scroll up again); we don’t have an Null constraint on the foreign key ParentId, so the Insert statement is excuted without exceptions, but in most cases in the world, DBA do put this constraint, by that we will get a “cannot insert Null value in ParentId” exception!

The solution is to set the Inverse attribute to “true”, which means that the Parent will NOT updated the Child objects foreign key, it will only call Session.Save(ch) due to the cascade attribute, so the result will be like the record 6:

dbValues2

But then how to solve this problem?! we want to set the value of ParentId AND be able to preserve the Null constraint; so we need to set the MyParent property of the Child to the Parent “par” like line 4:

    1 Parent par = Session.Get<Parent>(8);

    2             Child ch = new Child();

    3             ch.Name = “Emad”;

    4             ch.MyParent = par;

    5             par.MyChildren.Add(ch);

    6             Session.Save(par);

and to make it graceful, we can create custom collection for the Children and in the Add method of the collection we set the passed Child objects property MyParent to the parent.

You can download the code sample here.

I hope this is detailed enough to explain what the Inverse attribute exactly is, how to go about the Null exception, and to understand that the attributes “Inverse” and “cascade” are different things.

24 thoughts on “NHibernate Inverse attribute

  1. Erlis

    I don’t know how yo don’t have a comment yet! Very nice explanation! Thanks.

  2. Thanh Trung

    It’s really funny explanation that make me understand how the inverse attribute works. Thanks.

  3. raffaeu

    Very helpful thanks.
    Now I am just wondering using the inverse=true approach how you can:
    1) remove a child item
    2) clean up the whole collection of childs

  4. Francois Botha

    Ditto on what raffaeu said. I’m having aweful problems trying to:

    par.MyChildren.Clear();
    or
    par.MyChildren.Remove(ch);

    Could you append examples to your article?

    Thanks for a great article in any case.

  5. Emad Alashi Post author

    raffaeu and francois,

    first i appologize for this late response (probably you have forgotten about this post :P) I couldn’t answer before now due technical problems with my host.

    the answer is that you need to refresh these entities after the save and Flush() using the ISession.Refresh(). this is because you are using the basic class instance and it needs to be refreshed with the values from the database again.

    I hope this answers your question

  6. myro

    Thank you very much!
    finally a post that is nice to read and exhaustive!!!! thx!

  7. Idrees Haddad

    Salam Emad,

    I hope you’re glad to know that your post is still getting mileage (make sure you have ekhla9 fe al neeyah).

    to sum up:
    (this addition is inspired by TigerShark)
    If you are inserting an entity in a collection and saving the class
    containing the collection, NHibernate can do one of two things:

    1. (Inverse false “default”) Insert the child, insert the parent, and then update the foreign key of the child.

    2. (Inverse true): Insert the parent and then insert the child (of course you have to explicitly set the parent of the child before you save).

    good post :-)

  8. aric

    question.. your example shows you are only adding a child and your able to save the parentID because you had a session.Get.

    How would I go about saving a parent and child at the same time and have the parentID pass to the child ??

  9. asakura89

    thanks a lot for easy-to-learn explanations

  10. Pingback: NHibernate foreign key is null when saving | Jisku.com - Developers Network

  11. sajmon

    Nice post! but..
    I do not see update statement in console output.

    I have configuration:
    _sessionFactory = Fluently.Configure()
    .Database(MsSqlConfiguration.MsSql2008.ConnectionString(@”con str…”)
    .FormatSql()
    .ShowSql())

    But when I set ignore to false (in mapping), there are two insert and no update…

    Why the update statement isn’t printed to console?

    Someone knows?
    How to see sql update stageneeated

  12. Emad Alashi Post author

    @sajmon
    It’s been a long while since I used NHibernate and probably many things changed since then, if use SQL Profiler it might give you a faster answer than me try to reproduce your scenario.
    If you like to send me your sample project that would find too.

  13. sajmon

    @Emad Alashi
    Thank you for reply, I’ve resolved the problem.
    I have added the log4net and set the log level to DEBUG.

    if there is level=”INFO”, then not all sql are printed…

  14. Long

    The lase example, i saw code at line 5:
    par.MyChildren.Add(ch);

    You said “to make it graceful” it means not necessary? But if i remove thie line,
    it will not execute successful because the null ParentId

  15. Emad Alashi Post author

    Hi Long,

    It seems I have to revise my punctuation; that if is meant for the following sentence “we can create custom collection for the Children and… “

Leave a Reply

Your email address will not be published. Required fields are marked *