Transparent Query Batching Through Your Repository

15 commentsWritten on April 1st, 2009 by
Categories: NHibernate, Performance

All of our projects that use NHibernate (which is all of them except those where the customer explicitly doesn't want us to use it or where it wouldn't make sense to use it) use the same Repository implementation. After the Future and FutureValue queries were added to NHibernate, i modified the implementation of that Repository class.

Two of the FindAll methods now look like this:

        public virtual IEnumerable<T> FindAll()
        {
            return Session.CreateCriteria<T>().Future<T>();
        }
 
        public virtual IEnumerable<T> FindAll(DetachedCriteria criteria)
        {
            return criteria.GetExecutableCriteria(Session).Future<T>();
        }

The only thing i changed in those methods is calling the Future method, instead of the List method. That's it. All of our specific Find-methods (those that execute specific queries) pass through the FindAll(DetachedCriteria criteria) method so they all benefit from this change.

That means that all of our queries are suddenly batched transparently whenever possible, without impacting any of the calling code. And that is pretty nice if you ask me. Batching queries can offer a substantial performance benefit, and we didn't even have to change any of the calling code to achieve it.

Obviously, this only works for the queries that return IEnumerables (in our case, that's every query that doesn't return a single value). I also added a few more methods to enable query batching for queries that return a single entity, or a scalar value (i kept the original methods in this code snippet as well so you can see the difference):

        public virtual T FindOne(DetachedCriteria criteria)
        {
            return criteria.GetExecutableCriteria(Session).UniqueResult<T>();
        }
 
        public virtual IFutureValue<T> FindFutureOne(DetachedCriteria criteria)
        {
            return criteria.GetExecutableCriteria(Session).FutureValue<T>();
        }
 
        public virtual K GetScalar<K>(DetachedCriteria criteria)
        {
            return (K)criteria.GetExecutableCriteria(Session).UniqueResult();
        }
 
        public virtual IFutureValue<K> GetFutureScalar<K>(DetachedCriteria criteria)
        {
            return criteria.GetExecutableCriteria(Session).FutureValue<K>();
        }
 
        public virtual int Count(DetachedCriteria criteria)
        {
            return Convert.ToInt32(QueryCount(criteria).GetExecutableCriteria(Session).UniqueResult());
        }
 
        public virtual IFutureValue<int> FutureCount(DetachedCriteria criteria)
        {
            return QueryCount(criteria).GetExecutableCriteria(Session).FutureValue<int>();
        }

So let's recap. Queries that return IEnumerables are all batched transparently whenever it's possible to do so. No calling code had to be modified to get this benefit. Queries that return single values (an entity instance or a scalar value) that still use the 'old' FindOne, GetScalar and Count methods obviously couldn't benefit from the transparent batching without breaking backwards compatibility, but the new methods that were introduced do enable transparent batching for these queries from now on.

Does all of this sound too good to be true? I'd be skeptic too if i were you but i made these changes a few months ago actually and we have been using this stuff on a couple of projects with zero problems.

Obviously, you need NHibernate 2.1 Alpha 1 (or later) for this or the current trunk, both of which i would recommend over NH 2.0 at this point.

  • http://fgheysels.blogspot.com Frederik

    What’s the advantage of this ?
    Am I right to suppose that you can minimize roundtrips to the DB when using Future & FutureValue ?
    (Multiple statements are executed with one roundtrip if possible ? )

  • http://davybrion.com Davy Brion

    yeah, that’s what batching statements is usually about ;)

  • Valeriu Caraulean

    Does it fit with Linq to NHibernate?

  • http://davybrion.com Davy Brion

    No idea, never used it as i am waiting for the new Linq NH implementation before i try Linq queries with NHibernate

    I honestly prefer the Criteria API

  • Ken Tong

    Davy,

    Do you use query cache? It seems to me that Future() will make all queries become non-cacheable. Any experience you can share on this?

  • http://davybrion.com Davy Brion

    @Ken

    Indeed, queries are not cached when used with Future or MultiCriteria/MultiQuery directly

    i use the query cache in some occasions, but then my specific Find method for that query simply creates the criteria and executes it through the current session directly

  • Ken Tong

    Davy,

    Thanks. Just FYI, you could call MultiCriteria.SetCacheable(true) to make to whole batch cacheable.

    I would call MultiCriteria.SetCacheable(true) for the batch that I manually created. But I would not figure out an elegant way to work with caching Future() results.

  • http://davybrion.com Davy Brion

    hah, i didn’t even know MultiCriteria had the SetCacheable method :)

  • liviu

    What if i pass the collection on another thread?
    :-0

  • http://davybrion.com Davy Brion

    NHibernate sessions are inherently not thread safe, as stated in the docs.

  • http://blog.restimartinez.es Resti Martinez

    Attention, this test fails (for me) in SQL Server 2008 (note Future)

    using (var session = factory.OpenSession())
    using (var transaction = session.BeginTransaction()) { }
    {
    Person person = new Person();
    session.Save(person); //Person is mapped with native identity
    session.Delete(person);

    Assert.IsFalse(new List(session.CreateCriteria(typeof(Person)).Future()).Contains(person));

    transaction.Rollback();
    }

    However, this test work

    using (var session = factory.OpenSession())
    using (var transaction = session.BeginTransaction()) { }
    {
    Person user = new Person();
    session.Save(person); //Person is mapped with native identity
    session.Delete(person);

    Assert.IsFalse(session.CreateCriteria(typeof(Person)).List().Contains(person));

    transaction.Rollback();
    }

    i don’t know the cause

  • http://davybrion.com Davy Brion

    @Resti

    can you create a reproducable test case and create an issue at the nhibernate jira? (http://jira.nhforge.org/)

  • http://blog.restimartinez.es Resti Martinez
  • Pingback: The Inquisitive Coder – Davy Brion’s Blog » Blog Archive » Stop Exposing Collections Already!

  • Pingback: Elegant Code » Tips for ORM Data Access