Question

I am wondering under what circumstances the following NHibernate code could fail:

var session = NHibernateSessionManager.CurrentSession;

var foo = session.Linq<Foo>.ToList()[0];

foo.SomeProperty = "test";

session.SaveOrUpdate(foo);

var reloadedFoos = session.Linq<Foo>
                         .Where(x => x.SomeProperty == "test");

Assert.That(reloadedFoos.Count > 0);

The Assert statement always fails.

If I manually call session.Flush after SaveOrUpdate, then the select query succeeds, however I thought that we did not have to manually call flush? It was my understanding that NHibernate should be smart enough to realise that Foo has been updated, so the second select query should succeed.

Watching the SQL that is generated, it appears the second select query's SQL is executed before the first SaveOrUpdate's sql.

In fact, if I wrap the entire method in a transaction, then it succeeds:

using(NHibernateSessionManager.CurrentSession.BeginTransaction()
{
    // Same code as above
}

Now the SaveOrUpdate's sql will execute before the Linq.Where sql. This is a little strange, as I do not have to even commit the transaction in between.

What is going on?

Was it helpful?

Solution

I recommend you leverage NHibernate transactions. It's entirely possible that, without their use, NHibernate has no way of determining when to issue your SaveOrUpdate call.

You'll find that even read-only statements perform better when using transactions. Please see http://nhprof.com/Learn/Alert?name=DoNotUseImplicitTransactions for further details.

For example:

using(var session = NHibernateSessionManager.CurrentSession)
{
  using(var transaction = session.BeginTransaction())
  {
    var foo = session.Linq<Foo>.ToList()[0];

    foo.SomeProperty = "test";

    session.SaveOrUpdate(foo);
    transaction.Commit();
  }
}

OTHER TIPS

Note that you need a transaction for NHibernate to be "smart."

Here is how it works:

var session = NHibernateSessionManager.CurrentSession;
using(NHibernateSessionManager.CurrentSession.BeginTransaction()) {
    var foo = session.Linq<Foo>.ToList()[0];
    foo.SomeProperty = "test";
    var reloadedFoos = session.Linq<Foo>()
        .Where(x => x.SomeProperty == "test");
    Assert.That(reloadedFoos.Count > 0);
}

Note also that you do not call Save, Update, or SaveOrUpdate when you want to save the changes you have made to an object that the Session is already tracking back down to the database. NHibernate works differently from other ORMs: if it is tracking an object, then it will figure out when to send down the changes to the database and you don't need to tell it to do so.

"I am wondering under what circumstances the following NHibernate code could fail:" I think you have supplied at least one answer to your own question: when the code is run inside an implicit transaction. See this post from Ayende, which mentions inconsistent behavior inside implicit transactions. I have many unit tests that resemble your code except the test driver supplies a wrapping transaction, e.g.,

[Test]
public void Can_Update_Account() {
        Account account = PersistenceContext.Get<Account>(TEST_ACCOUNT_ID);

        string accountNumber = RandomString(10);
        account.AccountNumber = accountNumber;

        Account account1 = PersistenceContext.GetAll<Account>().Where(x => x.AccountNumber == accountNumber).SingleOrDefault();
        Account account2 = PersistenceContext.Get<Account>(account.Id);
        Assert.AreEqual(account.Id, account1.Id);
        Assert.AreEqual(accountNumber, account2.AccountNumber);
    }

[GetAll<>() is a thin wrapper over Linq<>.] I have many such tests that pass regularly.

You've probably got the incorrect flushmode set. You need to set the flushmode on the session to Auto for it to automatically flush the session before every query otherwise you need to flush the session manually to force the changes to be saved.

You have to close the session and create an now one before the Assert.

using(var session = NHibernateSessionManager.CurrentSession)
{
  using(var tx = session.BeginTransaction())
  {
    var foo = session.Linq<Foo>.ToList()[0];
    foo.SomeProperty = "test";
    session.SaveOrUpdate(foo);  
    tx.Commit();
  }
}

//create a new session here, the code depend if you use RhinoCommons (like me), no Rhino

using(var session = NHibernateSessionManager.CurrentSession)
{
  using(var tx = session.BeginTransaction())
  {
    var reloadedFoos = session.Linq<Foo>
            .Where(x => x.SomeProperty == "test");
    Assert.That(reloadedFoos.Count > 0);
    tx.Commit();
  }
}

If I manually call session.Flush after SaveOrUpdate, then the select query succeeds.

For a start: your call to SaveOrUpdate() is not even needed.

Here are some things to remember when using an NH session:

  • When you have Loaded an object from session, the session will continue to track changes to that object
  • Calling session.Update(entity) only tells the NHibernate session that it should start tracking the object, it does not go and write changes to the db

So, in your case, because you have loaded an object from the session already, calling session.Update() does nothing since it is already being tracked. You could infact force an update to the database by merely doing the following:

var session = NHibernateSessionManager.CurrentSession;
var foo = session.Linq<Foo>.ToList()[0];
foo.SomeProperty = "test";

session.Flush();

var reloadedFoos = session.Linq<Foo>
                         .Where(x => x.SomeProperty == "test");
Assert.That(reloadedFoos.Count > 0);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top