views:

34

answers:

3

I have two entities, a Shelf and a Product:

public class Shelf
{
    public virtual IList<Product> Products { get; set; }

    public Shelf()
    {
        Products = new List<Product>();
    }
}

public class Product
{
    public virtual string Name { get; set; }
}

Basically put, a Shelf can contain many Products, and a Product can be put on many Shelves. Thus, there's a unidirectional many-to-many relationship between Shelf and Product. When I run the following code:

var shelf = new Shelf();
var product = new Product { Name = "Peas" };
shelf.Products.Add(product);

using (var session = SessionFactory.OpenSession())
{
    session.Save(shelf);
}

NHibernate will save the Product and the Shelf, but it won't save the relationship between them, so if you retrieve the Shelf, it will contain zero Products.

However, if I add a session.Flush() call, it will save the relationship correctly so that retrieving the Shelf will return one Product.

This blog post details my issue:

http://www.neeraj.name/2005/07/02/hibernate-session-close-does-not-call-session-flush.html

However, I was wondering why I need this session.Flush() call. It seems counter-intuitive that saving a Shelf with the right cascade behavior will save the Product, but fail to establish the relationship between the two unless I flush the session. It seems to me that a Save() call should save everything, and not just the entity themselves without their relationships.

A: 

I don't know for sure but maybe its because of primary keys. When you are saving two trancident objects and using Identity as primary key generation model Nhibernate doesn't know the Id's for many-to-many joining table.

In real word usage you should always wrap all manipulations with entities in Transactions. When you commit transaction all changes will be flushed to data base. And before you commit it, why would you try to select already selected entity?

Sly
A: 

Are you using manual flush mode? If you have it set to auto, it will do it as soon as it needs to. If you are running within a transaction, you need to do session.getTransaction().commit() to make sure the change sticks. Hibernate will flush the session before running a query that might have the wrong result otherwise, or before committing the transaction.

You can configure hibernate to save a collection of child objects when the parent is saved. Read the section on transitive persistence (on the doc page linked below, in comments)

(Disclaimer: I use java hibernate, not NHibernate)

RMorrisey
Documentation explaining hibernate flush modes is here: http://docs.jboss.org/hibernate/stable/core/reference/en/html/objectstate.html#objectstate-flushing
RMorrisey
A: 

Save only marks an entity to be persisted and generates an identifier. It doesn't persist changes to the database.

All changes (inserts, updates and deletes) are performed when flushing, which can occur in different moments.

However, transactions should also be used always. The correct workflow is (roughly):

using (var session = factory.OpenSession())
using (var tx = session.BeginTransaction())
{
    //create and manipulate objects
    session.Save(newObjectToBePersisted);
    tx.Commit();
}
Diego Mijelshon
Thanks Diego, that answers my question perfectly!
Daniel T.