Disclaimer: The API presented here is still under development, so there might be changes until the final release. If you have any suggestions or comments post them here, over at Uservoice or drop me a mail!
In the last post I gave a quick overview about the new fluent query API. In this post we will explore one if the main interface that developers will interact with: the IFulltextQuery<T> interface. This interface provides all the necessary methods for building a query and retrieving the results from the Sphinx server. IFulltextQuery is a generic interface, where the generic type argument is a class that models the document that the Sphinx index contains.
Suppose we an index source defined like this (other fields omitted for brevity):
source product
{
sql_field_string = name
sql_field_string = description
sql_attr_float = price
sql_attr_uint = categoryid
sql_attr_uint = vendorid
sql_attr_float = weight
}
We would then define a class called Product like this:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public int VendorId { get; set; }
public int Weight { get; set; }
}
Note that we have also added a property named “Weight” to be able to retrieve the weight that Sphinx assigns to a match. We can then start querying the index like this:
FulltextStore fulltextStore = new FulltextStore();
using (IFulltextSession fulltextSession = fulltextStore.StartSession())
{
var results = fulltextSession.Query<Product>().
Match("a product").
Where(p => p.Price <= 10).
Results();
}
which will be translated to the following SphinxQL statement:
SELECT id AS c1, name AS c2, description AS c3, price AS c4, categoryid AS c5,
vendorid AS c6, weight() AS c7
FROM product
WHERE MATCH('a product') AND price <= 10.0
Note that the
Product class does not need to be marked with any attributes or have any mappings defined to be used for querying the index. The fluent query API uses conventions to translate class names to index names and property names to attribute names. It comes with a set of default conventions, but you will of course be able to specify your own conventions.
Ordering and Grouping
The IFulltextQuery interface exposes the following methods for ordering and grouping results:
IFulltextQuery<T> GroupBy<TKey>(Expression<Func<T, TKey>> keySelector);
IFulltextQuery<T> OrderBy<TKey>(Expression<Func<T, TKey>> keySelector);
IFulltextQuery<T> OrderByDescending<TKey>(Expression<Func<T, TKey>> keySelector);
IFulltextQuery<T> ThenBy<TKey>(Expression<Func<T, TKey>> keySelector);
IFulltextQuery<T> ThenByDescending<TKey>(Expression<Func<T, TKey>> keySelector);
IFulltextQuery<T> WithinGroupOrderBy<TKey>(Expression<Func<T, TKey>> keySelector);
IFulltextQuery<T> WithinGroupOrderByDescending<TKey>(Expression<Func<T, TKey>> keySelector);
There should be no big surprises here. In case you are wondering, OrderBy and ThenBy can be used interchangeably, ThenBy is intended to be used to improve the readability of a query when ordering by multiple keys. Additionally, we have WithinGroupOrderBy and WithinGroupOrderByDescending to define the sort order within a group. Here’s an example that uses some of these methods:
using (IFulltextSession fulltextSession = fulltextStore.StartSession())
{
var results = fulltextSession.Query<Product>().
Match("a product").
GroupBy(x => x.CategoryId).WithinGroupOrderBy(x => x.Price).
OrderBy(x => x.Name).
Results();
}
Changing Result Set Sizes
For limiting and expanding the size of a query result, the IFulltextQuery interface provides two methods: Take(int count) and Limit(int skip, int take). Both should be pretty much self-explanatory.
Setting Query Options
For setting the options for a query, the IFulltextQuery interface exposes a method called Options which takes a delegate as an argument, which can be used to make adjustments to the settings. The next example sets the ranker to SPH04, sets a field weight for the description and specifies a value of 50 for the maximum amount of documents to match. We also use the Take method to indicate that we want to retrieve all 50 results, because Sphinx by default limits the result set size to 20.
using (IFulltextSession fulltextSession = fulltextStore.StartSession())
{
var results = fulltextSession.Query<Product>().
Match("a product").
Options(o => o.Ranker(SphinxRankMode.SPH04).
FieldWeight(x => x.Description, 1000).
MaxMatches(50)).
Take(50).
Results();
}
Retrieving Query Metadata
The last thing we’re going to look at today, is how to retrieve meta data for a query, i.e. information like query execution time and keywords matched. For this, the Results method has an overload that takes an instance of a class named QueryMetaData as an out parameter:
using (IFulltextSession fulltextSession = fulltextStore.StartSession())
{
QueryMetadata metadata;
var results = fulltextSession.Query<Product>().
Match("a product").
Results(out metadata);
Console.WriteLine("{0} {1} {2}", metadata.Total, metadata.TotalFound, metadata.Time);
foreach (SphinxWordInfo wordInfo in metadata.WordInfo)
{
Console.WriteLine("{0} {1} {2}", wordInfo.Word, wordInfo.HitCount,
wordInfo.MatchingDocumentsCount);
}
}
That is all for now, in the next post we’ll be looking at aggregates, functions and result set projection.