NHibernate Search

time to read 4 min | 605 words

NHibernate Search is an extension to NHibernate that allows you to utilize Lucene.NET, a full text search engine as your query engine, instead of putting additional load on the database itself. In a sense, this is a good way outsource your queries from the database.

This has several chief advantages:

  • Your database is now mostly about performing queries by primary key (fast) and handling data storage, transactional semantics, etc. All of which should be very fast.
  • Your costly queries can now run on a different (cheap) machine, and a long query isn’t going to take locks in the database (slowing everything down).
  • Lucene.NET is a document database, this means that some things are significantly cheaper to query than in an RDBMS

Using NHibernate Search is very easy, from configuration stand point, we need to define the following listeners:

<listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search'
					type='post-insert'/>
<listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search'
					type='post-update'/>
<listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search'
					type='post-delete'/>

That done, we need to annotate our classes with attributes that will tell NHibernate Search how we want our entities to be indexed. Unlike NHibernate Validator, there is no alternate XML configuration for the indexing specification.

We annotate our entities like this:

[Indexed]
public class Post
{
	[DocumentId]
	public virtual int Id { get; set; }

	[IndexedEmbedded]
	public virtual Blog Blog { get; set; }

	[IndexedEmbedded]
	public virtual User User { get; set; }

	[Field(Index.Tokenized, Store = Store.Yes)]
	public virtual string Title { get; set; }

	[Field(Index.Tokenized)]
	public virtual string Text { get; set; }

	public virtual DateTime PostedAt { get; set; }

	public virtual ISet<Comment> Comments { get; set; }

	[IndexedEmbedded]
	public virtual ISet<Category> Categories { get; set; }

	[IndexedEmbedded]
	public virtual ISet<Tag> Tags { get; set; }
}

I am not going to go over the semantics of each attribute, and how they play together, suffice to say that Hibernate Search in Action will give you all the details about how to use this.

That said, let us look how this will actually get indexed:

image

We have a document, which has fields, and we can query those fields by any of its values, and get a pretty fast reply back. Note that the document structure that we have here is flat, so what would usually be a join is now an almost no cost operation. Of course, the more we put in the index, the bigger it is, but that is another tradeoff.

We can now query the index using:

using (var s = sf.OpenSession())
using(var search = Search.CreateFullTextSession(s))
using (var tx = s.BeginTransaction())
{
	var list = search.CreateFullTextQuery<Post>("Tags.Name:Hello")
		.SetMaxResults(5)
		.List<Post>();

	foreach (var post in list)
	{
		Console.WriteLine(post.Title);
	}

	tx.Commit();
}

As I said, actually getting down into all the syntax, options and tricks that we can use here is beyond the scope of this post, that is why I pointed out the book, which cover all of them in depth.