Are NHibernate ICriteria queries cached or put in the identity map?

1.9k views Asked by At

Using NHibernate I usually query for single records using the Get() or Load() methods (depending on if I need a proxy or not):

SomeEntity obj = session.Get<SomeEntity>(new PrimaryKeyId(1));

Now, if I execute this statement twice, like the example below, I only see one query being executed in my unittests:

SomeEntity obj1 = session.Get<SomeEntity>(new PrimaryKeyId(1));
SomeEntity obj2 = session.Get<SomeEntity>(new PrimaryKeyId(1));

So far, so good. But I noticed some strange behaviour when getting the same object using a ICriteria query. Check out my code below: I get the first object instance. I then change the value of a property to 10 (the value in the database is 8), get another instance and finally check the values of the second object instance.

//get the first object instance.
SomeEntity obj1 = session.CreateCriteria(typeof(SomeEntity))
                         .Add(Restrictions.Eq("Id", new PrimaryKeyId(1)))
                         .UniqueResult<SomeEntity>();

//the value in the database and the property is 8 at this point. Let's set it to 10.
obj1.SomeValue = 10;

//get the second object instance.
SomeEntity obj2 = session.CreateCriteria(typeof(SomeEntity))
                         .Add(Restrictions.Eq("Id", new PrimaryKeyId(1)))
                         .UniqueResult<SomeEntity>();

//check if the values match.
Assert.AreEqual(8, obj2.SomeValue);

Now, for some reason the assert fails, because the value is 10 of obj2 even though I asked for the object with a new query. the funny thing is, there are 2 exactly the same select queries being executed according to my unit test output window. My question: why are there 2 queries being executed if the second object is fetched from the first level cache?

Am I missing something or is this a bug?

Regards, Ted

edit #1: using NHibernate v2.1.2GA edit #2: I added some extra explanation about the 2 queries being executed to the last paragraph.

6

There are 6 answers

1
TedOnTheNet On BEST ANSWER

Well, having learned a lot more about NHibernate I can now answer this question myself: The ICriteria query returns a list of objects fetched by NHibernate. NHibernate does not know which objects are returned until they are matched one by one with the object in the first level cache. If the item is already in the first level cache map the item read from the database is discarded. if it is not in the identity map, the item is put into the first level cache.

Another "a-ha!" moment: suppose you run the query for the first time while there are 5 rows in the database all rows are fetched and put into first level cache. now over time 5 more records are added to the table and you rerun the query. Now all 10 records are fetched, but NHibernate sees 5 of them are already in the cache and will only add the 5 latter records. So basically you fetched 5 records for nothing (just to match the identifiers with the object identifiers in the identity map).

0
tom.dietrich On

I am not sure why a second query is ran, but the expected behavior of NHibernate is if you ask for the same object by ID from the same session, you get the first level cache.

2
Frederik Gheysels On

AFAIK, only 'Get' (and maybe Load) use the 1st level cache.

Using the Criteria API always results in a query hitting the DB, unless the 2nd level cache is enabled.

Edit: more information can be found here

3
Jamie Ide On

NHibernate is probably issuing an update between the first and second queries to protect you from a concurrency problem. As Frederik pointed out, you should always use Get to retrieve an object by its key.

I'm curious, what is the PrimaryKeyId wrapper adding?

EDIT:

However it's working (my money's still on an update before select), this behavior is by design. If you want to discard your in-memory object and load a new instance of it from the session, then Evict the original from the session first. There is also a Refresh method you could try.

2
Vadim On

Get/Load use the 1st level cache, this is why you don't see the 2nd call out the db. Queries do not use the 1st level cache. However, you can set up queries to use the 2nd level cache. See details here

UPDATE What's likely happening is the query is doing a 2 phase load. So it's getting the result set, but also checking the 1st level cache to see if any entities exist there. If they do, then it returns the cached object. See NHibernate.Loader.Loader.GetRow method. Here is the relevant line:

//If the object is already loaded, return the loaded one
obj = session.GetEntityUsingInterceptor(key);
0
sebd On

In my understanding, when using a Criteria, you are basically saying to NHibernate: "I want to filter rows based on expressions". When seen that way, NHibernate has no way of knowing if the query will always return the same filtered row(s) from the database, so it has to query it again.

Also, you can use query caching only with second-level caching, as per the documentation:

So the query cache should always be used in conjunction with the second-level cache.

From here