NHibernate search is the project from NHibernate contrib which allows you to integrate NHibernate and NLucene search. The basic idea of NHibernate search is similar to the NHibernate at common: if NHibernate allows you to map entities to the database tables, then NHibernate search allows to map entities to NLucene index documents (if you are not familiar with NLucene basic concepts, I recommend you book Lucene in action which is written for Java, but all described concepts are also suitable for .Net). Briefly NLucene index is document database which contains documents and each document has several fields.
Fluent NHibernate library simplifies mapping configuration: it allows you to write mappings on C# instead of xml which is used in NHibernate. Similarly Fluent NHibernate search allows you to write mappings from your POCO to the NLucene index documents (the same POCO which are used for mapping to the database tables via Fluent NHibernate). Let’s consider example – model for ecommerce application.
Suppose that we have Product class:
Each product may belong to several categories. Each category may have parent category and several child categories:
We want to search by product names. Mapping will look like this:
Here we specified map from Product class to the NLucene document. Store().Yes() means that index will store value of “Name” as is (so users will be able to search by whole name), Index().Tokenized() means that we want to tokenize name so users will be able to search by parts of the product name (full text search. Need to note here that for different languages it can be achieved by using different analyzers, but it is subject for another articles).
Everything is clear here. But then we need to add possibility for users to filter search by specific categories. In order to do this we need to store categories ids to the index somehow (add new field to the Product document). As you saw Product.Categories property has IList<Category>. The problem is that NLucene works with strings. So we need to convert IList<Category> to the string value. It can be done using custom field bridge. NHibernate search contains number of built-in bridges for dates, guids, strings. But in our case we need to write our own bridge – inheritor of IFieldBridge. It will retrieve all categories ids and concatenate them in string field:
Note that for each category of the current product we also add all parent categories (lines 12-23). So if users will search by parent category id they will also see products from all child categories. On the lines 25-31 we create new field and add it to the document.
Now we need to configure search mapping. Fluent NHibernate search allows to do it like this:
We don’t want to analyze Categories field – so we specified Index().UnTokenized(). However when you will compile and run the program, you will get the following exception:
NHibernate.HibernateException: Unable to guess IFieldBridge for Categories
at NHibernate.Search.Bridge.BridgeFactory.GuessType(String fieldName, Type fieldType, IFieldBridgeDefinition fieldBridgeDefinition, IDateBridgeDefinition dateBridgeDefinition)
at FluentNHibernate.Search.Mapping.Parts.FluentMappingPart..ctor(PropertyInfo propertyInfo)
at FluentNHibernate.Search.Mapping.Parts.FieldMappingPart..ctor(PropertyInfo propertyInfo)
at FluentNHibernate.Search.Mapping.DocumentMap`1.Map(Expression`1 property)
Exception occurs in FluentNHibernate.Search.Mapping.Parts.FluentMappingPart constructor:
On the line 10 it calls BridgeFactory.GuessType() method. Note that 3rd parameter (fieldBridgeDefinition) definition is null here, and as we have property of IList<Categories>, NHibernate search can’t find appropriate bridge at this moment and throws exception. As it shown in the search map configuration Bridge().Custom<CategoriesToStringBridge>() is called after Map() method:
So when FluentMappingPart constructor is called it doesn’t know yet about custom bridge.
In order to fix it, we can use the following simple workaround: enclose call to BridgeFactory.GuessType() with try/catch block:
Now the exception won’t be re-thrown and our custom bridge will be successfully applied for the Categories properties. When you will re-build your search index – you can use e.g. Luke tool (you need to install Java to use it. However it works with index built by NLucene) to check that Product documents contain Categories field which contains categories ids, separated by comma. So you will be able to filter search results by categories (how to do it is the subject for another post).