tags:

views:

120

answers:

3

All right, I've seen some posts asking almost the same thing but the points were a little bit different.

This is a classic case: I'm saving/updating an entity and, within the SAME SESSION, I'm trying to get them from the database (using criteria/find/enumerable/etc) with FlushMode = Auto. The matter is: NHibernate isn't flushing the updates before querying, so I'm getting inconsistent data from the database.

"Fair enough", some people will say, as the documentation states:

This process, flush, occurs by default at the following points:

  • from some invocations of Find() or Enumerable()
  • from NHibernate.ITransaction.Commit()
  • from ISession.Flush()

The bold "some invocations" clearly says that NH has no responsibility at all. IMO, though, we have a consistency problem here because the doc also states that:

Except when you explicity Flush(), there are absolutely no guarantees about when the Session executes the ADO.NET calls, only the order in which they are executed. However, NHibernate does guarantee that the ISession.Find(..) methods will never return stale data; nor will they return the wrong data.

So, if I'm using CreateQuery (Find replacement) and filtering for entities with property Value = 20, NH may NOT return entities with Value = 30, right? But that's what happens in fact, because the Flush is not happening automatically when it should.

public void FlushModeAutoTest()
{
    ISession session = _sessionFactory.OpenSession();
    session.FlushMode = FlushMode.Auto;

    MappedEntity entity = new MappedEntity() { Name = "Entity", Value = 20 };
    session.Save(entity);

    entity.Value = 30;
    session.SaveOrUpdate(entity);

    // RETURNS ONE ENTITY, WHEN SHOULD RETURN ZERO
    var list = session.CreateQuery("from MappedEntity where Value = 20").List<MappedEntity>();

    session.Flush();
    session.Close();
}

After all: am I getting it wrong, is it a bug or simply a non predictable feature so everybody have to call Flush to assure its work?

Thank you.

Filipe

A: 

managing and tuning hibernate is an artform.

why do you set an initial value of 20, save, then change it to 30?

As a matter of practice, if you are going modify the session, then query the session, you might want to explicitly flush between those operations. You might have a slight performance hit (after all, you then don't let hibernate optimize session flushing), but you can revisit if it becomes a problem.

You quoted that "session.find methods will never return stale data". I would modify your code to use a find instead of createQuery to see if it works.

hvgotcodes
@hvgotcodes: I'm setting and changing to test the behavior, my real code doesn't look like this. I'm using CreateQuery because Find is deprecated and CreateQuery is the suggested replace to it.
jfneis
+2  A: 

If you think about it, the query in your example must always go to the db. The session is not a complete cache of all records in the db. So there could be other entities with the value of 20 on disk. And since you didn't commit() a transaction or flush() the session NH has no way to know which "view" you want to query (DB | Session).

It seems like the "Best Practice" is to do everything (gets & sets) inside of explicit transactions:

using(var session = sessionFactory.OpenSession()) 
using(var tx = session.BeginTransaction()) 
{ 
    // execute code that uses the session 
    tx.Commit(); 
}

See here for a bunch of details.

David Lynch
@david: we agree that the session isn't a full database cache, and, obviously, NH will query the DB and there will have another records. Here comes the deal: NH's doc says that NH'll take care of this data inside session and commit it **automatically**. I agree that commiting the tx or flushing the session'll make it work, but so can we deduce that the docs are outdated? Or worst than that: can we deduce that it worked once but now it doesn't anymore?
jfneis
+3  A: 

I'm not very familiar with the NHibernate source code but this method from the ISession implementation in the 2.1.2.GA release may answer the question:

/// <summary>
/// detect in-memory changes, determine if the changes are to tables
/// named in the query and, if so, complete execution the flush
/// </summary>
/// <param name="querySpaces"></param>
/// <returns></returns>
private bool AutoFlushIfRequired(ISet<string> querySpaces)
{
    using (new SessionIdLoggingContext(SessionId))
    {
        CheckAndUpdateSessionStatus();
        if (!TransactionInProgress)
        {
            // do not auto-flush while outside a transaction
            return false;
        }
        AutoFlushEvent autoFlushEvent = new AutoFlushEvent(querySpaces, this);
        IAutoFlushEventListener[] autoFlushEventListener = listeners.AutoFlushEventListeners;
        for (int i = 0; i < autoFlushEventListener.Length; i++)
        {
            autoFlushEventListener[i].OnAutoFlush(autoFlushEvent);
        }
        return autoFlushEvent.FlushRequired;
    }
}

I take this to mean that auto flush will only guarantee consistency inside a transaction, which makes some sense. Try rewriting your test using a transaction, I'm very curious if that will fix the problem.

Jamie Ide
@jamie: tks God, you're right! :) Using an explicit transaction around the code block solved the problem. We can use criterias, create queries, whatever, changes got commited and the query result passes the test. Thank you again!I'll suggest to NH team to update documentation and clearly states that auto flush functionality only works inside an explicit transaction, hoping this help future doubts.
jfneis
You're welcome. Great question, I never understood this until now.
Jamie Ide