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.
April 1st, 2009 at 3:27 pm
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 ? )
April 1st, 2009 at 3:53 pm
yeah, that’s what batching statements is usually about
April 1st, 2009 at 4:09 pm
Does it fit with Linq to NHibernate?
April 1st, 2009 at 4:11 pm
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
April 2nd, 2009 at 7:04 am
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?
April 2nd, 2009 at 9:22 am
@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
April 2nd, 2009 at 11:48 am
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.
April 2nd, 2009 at 12:05 pm
hah, i didn’t even know MultiCriteria had the SetCacheable method
April 17th, 2009 at 10:23 am
What if i pass the collection on another thread?
:-0
April 17th, 2009 at 10:25 am
NHibernate sessions are inherently not thread safe, as stated in the docs.
October 5th, 2009 at 12:37 pm
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
October 5th, 2009 at 12:40 pm
@Resti
can you create a reproducable test case and create an issue at the nhibernate jira? (http://jira.nhforge.org/)
October 5th, 2009 at 3:25 pm
Thanks Davy.
http://nhjira.koah.net/browse/NH-1982
October 28th, 2009 at 7:01 am
[...] 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. [...]
November 13th, 2009 at 12:37 am
[...] requests to your services and database, especially if you are in a web or distributed [...]