tags:

views:

334

answers:

2

Hi, Can someone explain this little mystery to me about how NHibernate handles composite elements.

I have classes that look like this;

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

    public virtual ISet<Comment> Comments
    {
        get;
        set;
    }
}

public class Comment
{
    public virtual string CommentText
    {
        get;
        set;
    }

    public virtual DateTime Date
    {
        get;
        set;
    }
}

and mappings like this;

<class name="Blog" table="blog">
    <id name="Id" column="id" unsaved-value="0">
      <generator class="hilo"/>
    </id>

    <set name="Comments" table="blog_comments">
      <key column="blog_id" />
      <composite-element class="Comment">
        <property name="CommentText" column="comment" not-null="true" />
        <property name="Date" column="date" not-null="true" />
      </composite-element>
    </set>

  </class>

However when i perform a select like this;


using (ITransaction transaction = session.BeginTransaction())
{
    Blog blog = session.CreateCriteria(typeof(Blog))
                  .SetFetchMode("Comments", FetchMode.Eager)
                  .Add(Expression.IdEq(2345))            
                  .UniqueResult();

    transaction.Commit();
}

NHibernate issues a select with a join to get the blog with posts BUT then deletes all comments and then inserts the comments! Why is it doing this? If i do not use a transaction then it will ONLY perform the select and not the DELETE and INSERT as I would expect. What am I missing? I am using NHibernate 2.0

A: 

My question would be why you are committing if you only need to do a select? I believe the reason it's deleting all the comments is that when you call commit on the transaction, the blog object and it's associated comments are cached in the session that is used to create the transaction. When you call the commit, you are causing all the objects in the session to be saved which is causing the save back to the database. I'm not clear on why it's deleting the comments but it is correct behaviour to save the objects.

I also stumbled upon this today:

NHibernate is deleting my entire collection and recreating it instead of updating the table.

This generally happens when NHibernate can't figure out which items changed in the collection. Common causes are:

  • replacing a persistent collection entirely with a new collection instance
  • passing NHibernate a manually constructed object and calling Update on it.
  • serializing/deserializing a persistent collection apparently also causes this problem.
  • updating a with inverse="false" - in this case, NHibernate can't construct SQL to update an individual collection item.

Thus, to avoid the problem:

  • pass the same collection instance that you got from NHibernate back to it (not necessarily in the same session),
  • try using some other collection instead of ( or ), or
  • try using inverse="true" attribute for .
lomaxx
I thought that commit would only update the entities that have been altered - as none have why would it attempt to update anything? I also read this page http://ayende.com/Blog/archive/2008/12/28/nh-prof-alerts-use-of-implicit-transactions-is-discouraged.aspx
Gareth
I agree that you shouldn't use implicit transactions, I was just unclear on why you would commit the transaction if nothing had been altered. In any case, I like this question and the answer has been very informative!
lomaxx
+3  A: 

I think you need to override Equals() and GetHashCode() on Comment. NHibernate doesn't have an ID to go on for entity equality so you have to define what makes a comment entity equal to another comment.

Could be wrong :)


Edit

From: http://www.nhforge.org/doc/nh/en/index.html#components-incollections (7.2)

Note: if you define an ISet of composite elements, it is very important to implement Equals() and GetHashCode() correctly.

And an example of implementing Equals / GetHashCode

http://www.nhforge.org/doc/nh/en/index.html#persistent-classes-equalshashcode (4.3)

eyston
Thanks, I had implemented these in my full version but must have done something wrong in the override for GetHashCode, the example you provided works like a charm. Thank you again.
Gareth