I have been staring at the following screen for the final five minutes of my train ride. I spent these five minutes wishing I could wrap up some intriguing code, wishing I hadn’t accidentally pushed the F1 key and wishing Visual Studio would simply launch the bloody help program in another process.
Ayende recently mentioned the importance of using transactions with NHibernate:
“NHibernate assume that all access to the database is done under a transaction, and strongly discourage any use of the session without a transaction.”
In some ways, I wish it wasn’t possible to use sessions without transactions as it seems to cause unpredictable results. Take, for example, the following code from a unit test (for simplicity, the code has been slightly modified to only refer to NHibernate interfaces):
using (var session = SessionFactory.OpenSession())
var item = RunningTask.Start ();
RunningTask entryFromRepo = session.Linq<RunningTask> ()
.Single (x => x.Id == item.Id);
Assert.AreSame (item, entryFromRepo);
When using integer identity fields, this test passed with no issues. Once I decided to switch to GUIDs, the test began to fail.
Removing LINQ to NHibernate from the Equation
My first order of business when diagnosing such issues has been to remove LINQ to NHibernate from the equation by using the NHibernate Criteria API directly. I do this simply because LINQ to NHibernate has not yet received a production ready stamp of approval. Once again, however, I am glad to conclude that the LINQ to NHibernate is not to blame; in fact, I am quite impressed with how well it has worked for me thus far. Unfortunately, this didn’t help make the test pass, and I still needed to understand where exactly I was going wrong.
Good Old Fashion Debugging
The Criteria API was refusing to return a result when searching for the entity by ID. ISession.Get, on the other hand, did return a result, but I would return to this issue later. First, I wanted to figure out why the Criteria API was not giving me the result I expected, so I fired up SQL Server Profiler. I could see the Criteria API constructing and executing SQL queries to retrieve results, but there was no sign of a row being inserted before the query. It turns out, as Steve Bohlen previously pointed out, database assigned identity values force NHibernate to execute INSERTs immediately when ISession.Save is called. When I switched to GUIDs and stopped letting the database assign identity values, NHibernate stopped inserting the row during the ISession.Save call. The SQL query executed by the Criteria API therefore returned zero results.
I noticed the test was not making use of transactions. Having previously been bitten by the use of sessions without transactions, I decided to run the test within a transaction. Viola! If a transaction is active, NHibernate performs outstanding inserts when we request information from the database via the Criteria API and consequently LINQ to NHibernate. Now that the records were being inserted into the database, the Criteria API was returning the expected result! This left me wondering why the ISession.Get method worked when I ran the test without transactions.
As I suspected, there is some caching happening when entities are retrieved using the Get method. After digging through the NHibernate codebase, I found that the default implementation first tries to load an entity from the session-level cache and then the second-level cache before hitting the database. I presume Get is returning the instance stored in the session-level cache.
This bit of debugging and source code reading reiterated to me some key points that should be considered when using NHibernate:
- Use transactions. They are crucial when writing, and they are also important when reading from the database since transactions ensure consistency across queries.
- When searching for an entity by Id, use ISession.Get rather than LINQ to NHibernate or the NHibernate Criteria API. It could save you a trip to the database.