The Inquisitive Coder – Davy Brion's Blog

Trying to walk that thin line between intelligence and ignorance

Transparent Query Batching Through Your Repository

Posted by Davy Brion on April 1st, 2009

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.

15 Responses to “Transparent Query Batching Through Your Repository”

  1. Frederik Says:

    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 ? )

  2. Davy Brion Says:

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

  3. Valeriu Caraulean Says:

    Does it fit with Linq to NHibernate?

  4. Davy Brion Says:

    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

  5. Ken Tong Says:

    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?

  6. Davy Brion Says:

    @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

  7. Ken Tong Says:

    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.

  8. Davy Brion Says:

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

  9. liviu Says:

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

  10. Davy Brion Says:

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

  11. Resti Martinez Says:

    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

  12. Davy Brion Says:

    @Resti

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

  13. Resti Martinez Says:

    Thanks Davy.

    http://nhjira.koah.net/browse/NH-1982

  14. The Inquisitive Coder – Davy Brion’s Blog » Blog Archive » Stop Exposing Collections Already! Says:

    [...] The problem that i have with exposing a collection’s values through a type which enables other pieces of code to modify the state of that collection is that it is effectively a pretty big breach of encapsulation. You no longer have sole control over the contents of the collection. Anyone can effectively add and remove elements from the collection, or even clear them entirely, without your object knowing about it. Obviously, this can cause subtle side-effects in the behavior of your object. Which can (and sooner or later will) lead to some quality time between you and your debugger. You also no longer have the ability to easily change the type of collection you’re using which might prevent certain future improvements that you can make to a class. And in case you’re wondering why you’d want to change the type of collection, check out an example where the benefit was huge. [...]

  15. Elegant Code » Tips for ORM Data Access Says:

    [...] requests to your services and database, especially if you are in a web or distributed [...]

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>