One of the nicest new features in NHibernate 2.1 is the Future<T>() and FutureValue<T>() functions. They essentially function as a way to defer query execution to a later date, at which point NHibernate will have more information about what the application is supposed to do, and optimize for it accordingly. This build on an existing feature of NHibernate, Multi Queries, but does so in a way that is easy to use and almost seamless.
Let us take a look at the following piece of code:
using (var s = sf.OpenSession()) using (var tx = s.BeginTransaction()) { var blogs = s.CreateCriteria<Blog>() .SetMaxResults(30) .List<Blog>(); var countOfBlogs = s.CreateCriteria<Blog>() .SetProjection(Projections.Count(Projections.Id())) .UniqueResult<int>(); Console.WriteLine("Number of blogs: {0}", countOfBlogs); foreach (var blog in blogs) { Console.WriteLine(blog.Title); } tx.Commit(); }
This code would generate two queries to the database:
Two queries to the database is a expensive, we can see that it took us 114ms to get the data from the database. We can do better than that, let us tell NHibernate that it is free to do the optimization in any way that it likes, I have marked the changes in red:
using (var s = sf.OpenSession()) using (var tx = s.BeginTransaction()) { var blogs = s.CreateCriteria<Blog>() .SetMaxResults(30) .Future<Blog>(); var countOfBlogs = s.CreateCriteria<Blog>() .SetProjection(Projections.Count(Projections.Id())) .FutureValue<int>(); Console.WriteLine("Number of blogs: {0}", countOfBlogs.Value); foreach (var blog in blogs) { Console.WriteLine(blog.Title); } tx.Commit(); }
Now, we seem a different result:
Instead of going to the database twice, we only go once, with both queries at once. The speed difference is quite dramatic, 80 ms instead of 114 ms, so we saved about 30% of the total data access time and a total of 34 ms.
To make things even more interesting, it gets better the more queries that you use. Let us take the following scenario. We want to show the front page of a blogging site, which should have:
For right now, we will ignore caching, and just look at the queries that we need to handle. I think that you can agree that this is not an unreasonable amount of data items to want to show on the main page. For that matter, just look at this page, and you can probably see as much data items or more.
Here is the code using the Future options:
using (var s = sf.OpenSession()) using (var tx = s.BeginTransaction()) { var blogs = s.CreateCriteria<Blog>() .SetMaxResults(30) .Future<Blog>(); var posts = s.CreateCriteria<Post>() .AddOrder(Order.Desc("PostedAt")) .SetMaxResults(10) .Future<Post>(); var tags = s.CreateCriteria<Tag>() .AddOrder(Order.Asc("Name")) .Future<Tag>(); var countOfPosts = s.CreateCriteria<Post>() .SetProjection(Projections.Count(Projections.Id())) .FutureValue<int>(); var countOfBlogs = s.CreateCriteria<Blog>() .SetProjection(Projections.Count(Projections.Id())) .FutureValue<int>(); var countOfComments = s.CreateCriteria<Comment>() .SetProjection(Projections.Count(Projections.Id())) .FutureValue<int>(); Console.WriteLine("Number of blogs: {0}", countOfBlogs.Value); Console.WriteLine("Listing of blogs"); foreach (var blog in blogs) { Console.WriteLine(blog.Title); } Console.WriteLine("Number of posts: {0}", countOfPosts.Value); Console.WriteLine("Number of comments: {0}", countOfComments.Value); Console.WriteLine("Recent posts"); foreach (var post in posts) { Console.WriteLine(post.Title); } Console.WriteLine("All tags"); foreach (var tag in tags) { Console.WriteLine(tag.Name); } tx.Commit(); }
This generates the following:
And the actual SQL that is sent to the database is:
SELECT top 30 this_.Id as Id5_0_, this_.Title as Title5_0_, this_.Subtitle as Subtitle5_0_, this_.AllowsComments as AllowsCo4_5_0_, this_.CreatedAt as CreatedAt5_0_ FROM Blogs this_ SELECT top 10 this_.Id as Id7_0_, this_.Title as Title7_0_, this_.Text as Text7_0_, this_.PostedAt as PostedAt7_0_, this_.BlogId as BlogId7_0_, this_.UserId as UserId7_0_ FROM Posts this_ ORDER BY this_.PostedAt desc SELECT this_.Id as Id4_0_, this_.Name as Name4_0_, this_.ItemId as ItemId4_0_, this_.ItemType as ItemType4_0_ FROM Tags this_ ORDER BY this_.Name asc SELECT count(this_.Id) as y0_ FROM Posts this_ SELECT count(this_.Id) as y0_ FROM Blogs this_ SELECT count(this_.Id) as y0_ FROM Comments this_
That is great, but what would happen if we would use List and UniqueResult instead of Future and FutureValue?
I’ll not show the code, since I think it is pretty obvious how it will look like, but this is the result:
Now it takes 348ms to execute vs. 259ms using the Future pattern.
It is still in the 25% – 30% speed increase, but take note about the difference in time. Before, we saved 34 ms. Now, we saved 89 ms.
Those are pretty significant numbers, and those are against a very small database that I am running locally, against a database that is on another machine, the results would have been even more dramatic.