Logo

NHibernate

The object-relational mapper for .NET

NHibernate Futures

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:

image

image

image

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:

image

image

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:

  • A grid that allow us to page through the blogs.
  • Most recent posts.
  • All categories
  • All tags
  • Total number of comments
  • Total number of posts

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:

image

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:

image

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.


Posted Sun, 26 April 2009 08:33:00 PM by Ayende
Filed under: Future

comments powered by Disqus
© NHibernate Community 2024