Fluent Search API
Examine offers a fluent (chainable) search API which aims to make constructing complex searches simple. The underlying API is determined by the provider implementation, with Examine just exposing the appropriate methods.
An example of the fluent API in action is as follows:
ISearchCriteria sc = ExamineManager.Instance.CreateSearchCriteria(100 /* max record count */, IndexType.Content /* type of index to query */);
IBooleanOperation query = sc.NodeName("Examine").And().Range("createdDate", new DateTime(2010, 03, 21), new DateTime(2010, 03, 31); // will match all nodes which have the name Examine created between the 21st and the 31st March 2010).
IEnumerable<SearchResult> results = ExamineManager.Instance.Search(query.Compile()); // prepares the query to be handled by the searcher
The ISearchCriteria interface is the real workhorse of the API, it’s the first interface you start with, and it’s the last interface you deal with. In fact, ISearchCriteria implements IQuery, meaning that all the query operations start here.
In addition to query operations there are several additional properties for such as the maximum number of results and the type of data being searched.
Because ISearchCriteria is tightly coupled with the BaseSearchProvider implementation it is actually created via a factory pattern, like so:
ISearchCriteria searchCriteria = ExamineManager.Instance.SearchProviderCollection["MySearcher"].CreateSearchCriteria(100, IndexType.Content);
What we’re doing here is requesting that our BaseSearchProvider creates an instance of an ISearchCriteria. It takes two parameters:
- int maxResults
- Examine.IndexType indexType
This data can/ should be then used by the search method to return what’s required.
The IQuery interface is really the heart of the fluent API, it’s what you use to construct the search for your site. Since Examine is designed to be technology agnostic the methods which are exposed via IQuery are fairly generic. A lot of the concepts are borrowed
from Lucene.Net, but they are fairly generic and should be viable for any searcher.
The IQuery API exposes the following methods:
- IBooleanOperation Id(int id);
- IBooleanOperation NodeName(string nodeName);
- IBooleanOperation NodeName(IExamineValue nodeName);
- IBooleanOperation NodeTypeAlias(string nodeTypeAlias);
- IBooleanOperation NodeTypeAlias(IExamineValue nodeTypeAlias);
- IBooleanOperation ParentId(int id);
- IBooleanOperation Field(string fieldName, string fieldValue);
- IBooleanOperation Field(string fieldName, IExamineValue fieldValue);
- IBooleanOperation MultipleFields(IEnumerable<string> fieldNames, string fieldValue);
- IBooleanOperation MultipleFields(IEnumerable<string> fieldNames, IExamineValue fieldValue);
- IBooleanOperation Range(string fieldName, DateTime start, DateTime end);
- IBooleanOperation Range(string fieldName, DateTime start, DateTime end, bool includeLower, bool includeUpper);
- IBooleanOperation Range(string fieldName, int start, int end);
- IBooleanOperation Range(string fieldName, int start, int end, bool includeLower, bool includeUpper);
- IBooleanOperation Range(string fieldName, string start, string end);
- IBooleanOperation Range(string fieldName, string start, string end, bool includeLower, bool includeUpper);
As you can see all the methods within the IQuery interface return an IBooleanOperator, this is how the fluent API works!
Hopefully it’s fairly obvious what each of the methods are, but the one you’re most likely to use is Field. Field allows you to specify any field in your index, and then provide a word to lookup within that field.
You’ve probably noticed the IExamineValue parameter which is passable to a lot of the different methods, methods which take a string, but what is IExamineValue?
Well obviously it’s some-what provider dependant, so I’ll talk about it as part of Umbraco Examine, as that’s what I think most initial uptakers will want.
Because Lucene supports several different
for text we decided it would be great to have those exposed in the API for people to leverage. For this we’ve got a series of string extension methods which reside in the namespace
So once you add a using statement for that you’ll have the following extension methods:
- public static IExamineValue SingleCharacterWildcard(this string s)
- public static IExamineValue MultipleCharacterWildcard(this string s)
- public static IExamineValue Fuzzy(this string s)
- public static IExamineValue Fuzzy(this string s, double fuzzieness)
- public static IExamineValue Boost(this string s, double boost)
- public static IExamineValue Proximity(this string s, double proximity)
- public static IExamineValue Excape(this string s)
All of these return an IExamineValue (which UmbracoExamine internally handles), and it tells Lucene.Net how to handle the term modifier you required.
I wont repeat what is said within the Lucene documentation, I suggest you read that to get an idea of what to use and when.
The only exceptions are Escape.
If you’re wanting to search on multiple works together then Lucene requires them to be ‘escaped’, otherwise it’ll (generally) treat the space character as a break in the query. So if you wanted to search for Umbraco Rocks and didn’t escape it you’d match on
both Umbraco and Rocks, where as when it’s escaped you’ll then match on the two words in sequence.
IBooleanOperation allows your to join multiple IQuery methods together using:
- IQuery And()
- IQuery Or()
- IQuery Not()
These are then translated into the underlying searcher so it can determine how to deal with your chaining. At the time of writing we don’t support nested conditionals (grouped OR’s operating like an And).
Each operator will then return an IQuery which will allow you to chain another query method into it.
There’s another method on IBooleanOperation which doesn’t fall into the above, but it’s very critical to the overall idea:
The Compile method will then return an ISearchCriteria which you then pass into your searcher. It’s expected that this is the last method which is called and it’s meant to prepare all search queries for execution.
The reason we’re going with this rather than passing the IQuery into the Searcher is that it means we don’t have to have the max results/ etc into every IQuery instance, it’s not something that is relevant in that scope, so it’d just introduce code smell, and
no one wants that.
var sc = ExamineManager.Instance.CreateSearchCriteria();
var query = sc.NodeName("umbraco").And().Field("bodyText", "is awesome".Escape()).Or().Field("bodyText", "rock".Fuzzy());
var results = ExamineManager.Instance.Search(query.Compile());