aggietech
aggietech

Reputation: 978

nHibernate second level cache not working on query over

I have the following problem. I've enabled the second level caching system on nHibernate (using Postgres) with the following configurations

cfg.SessionFactory().Caching.Through<RtMemoryCacheProvider>().WithDefaultExpiration(28800);

I'm enabling only enabling entity caching because i do not need query caching at this point.

In my entities here are my settings (they are some are read-write, some are read-only, more can be made read-only at this point)

<id name="StudentID" access="property" column="`StudentID`">
  <generator class="native" />
</id>

<property name="Name" column="`Name`" >
<property name="Address" column="`Address`" />
<property name="IsActive" column="`IsActive`" />
<property name="DateCreated" column="`DateCreated`" />
<property name="DateLastUpdated" column="`DateLastUpdated`" />
<property name="LastUpdatedBy" column="`LastUpdatedBy`" />

<set name="Projects" inverse="true" mutable="false">
  <cache usage="read-only"/>
  <key column="`StudentID`" />
  <one-to-many class="Project" />
</set>

<set name="Classes" inverse="true" mutable="false">
  <cache usage="nonstrict-read-write"/>
  <key column="`StudentID`" />
  <one-to-many class="Class" />
</set>

<set name="Books" inverse="true" mutable="false">
  <cache usage="nonstrict-read-write"/>
  <key column="`StudentID`" />
  <one-to-many class="Book" />
</set>
</class>

When unit testing my solution - i first pre-fetch a list of students, and then try to generate a cache hit

public bool PreLoadStudents()
{
    using (ISession session = NHibernateHelper.OpenSession())
    {
        IList<Student> results = session.QueryOver<Student>()
                                 .Fetch(d => d.Projects).Eager
                                 .Fetch(d => d.Classes).Eager
                                 .Fetch(d => d.Books).Eager
                                 .TransformUsing(Transformers.DistinctRootEntity)
                                 .List<Student>();
     }
}

[Test]
public void GetByIdTest()
{
   bool bLoaded = testBLL.PreLoadStudents();

   var student1 = testBLL.GetByID("123");
   var student2 = testBLL.GetByID("123");

   long cacheHit = testBLL.GetSessionFactory().Statistics.SecondLevelCacheHitCount;

   Assert.That(cacheHit,Is.EqualTo(2));    
}

I've tried out two different implementation of "GetByID", one of them uses the convention "get" method, the other uses the query over method with fetch statements similar to the PreLoadStudents student method.

In the case of "get" method, both cache hit occurred and the test passes. In the case of "query over", no cache hit or misses occur, but 2 queries were executed instead.

Here's the code i used for the "GetByID" method using the "Get" method

var student = session.Get<Student>(studentId); 

I do not prefer this method because i'm unable to fetch child collections that are lazy-loaded

Here's the code i used for the "GetByID" method using the "QueryOver" method

 var student = session.QueryOver<Student>()
                                          .Where(d => d.studentId == currentStudentId)
                                          .Fetch(d => d.Projects).Eager
                                          .Fetch(d => d.Classes).Eager
                                          .Fetch(d => d.Books).Eager
                                          .SingleOrDefault();

Any thoughts on why the "get" method generated a hit while the query over method did not?

Upvotes: 0

Views: 1762

Answers (2)

aggietech
aggietech

Reputation: 978

After reading and doing some experimental testing, here's the solution that emerged for my question.

My initial question - Any thoughts on why the "get" method generated a hit while the query over method did not? is a mis-informed question, here's why:

  1. Second level cache (L2) in nHibernate caches the values of the entities
  2. Query cache in nHibernate caches the index of the search result

Thus

  1. Get allows us to quickly retrieve an entity if we know the id. If we do not know the id then we will need to do a query that will hit the database if it has not been ran before. The alternative to this is to load all from the cache and then run LINQ on top of it.
  2. Query over allows us to store query results in the query cache that can be populated from the L2 cache.

Lessons learned

  1. Turn on both L2 cache and query cache if you want to cache queries, query cache without L2 probably doesn't improve the performance much
  2. L2 can only be accessed with a cached query or via GET

Upvotes: 2

Jeroen
Jeroen

Reputation: 11

Second-level cache only works if you have a transaction, which is also a good practice around queries anyway.

using (ISession session = NHibernateHelper.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
    IList<Student> results = session.QueryOver<Student>()
                             .Fetch(d => d.Projects).Eager
                             .Fetch(d => d.Classes).Eager
                             .Fetch(d => d.Books).Eager
                             .TransformUsing(Transformers.DistinctRootEntity)
                             .List<Student>();

    tx.Commit();
}

Upvotes: 1

Related Questions