Click or drag to resize

Prepared Queries

To give users an opertunity to improve the performance of their queries when using the fluent API, SphinxConnector.NET 5.0 introduced prepared (sometimes also called compiled) queries for the fluent API. The idea is to create a query template that is parsed once and then reused on the following executions thus alleviating the overhead caused by parsing the expression tree and generating the SphinxQL statement. This is similar to prepared statements implemented by a DBMS, where a statement is send to the server only once and subsequently just the query parameters are being transmitted.

As a bonus, this is also a nice way to organize your queries.

Note Note

Prepared queries should support the same features that regular fluent API queries do, with one exception (see below). Nevertheless, you should verify that the correct statement is generated for your queries. This can be done by turning on logging. If you encounter a query that does execute properly via Query but fails as a prepared query, please drop us a line.

Not supported at the moment is passing parameters to your own extension methods on IFulltextQueryTDocument. This might be supported in future versions.

Using Prepared Queries

To create a prepared query we need to create a class which implements the IPreparedQuery interface. In the following example we'll create prepared queries for the Product document:

Document Model
public class Product    
{
    public Product()
    {
        Properties = new Dictionary<string, object>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }

    public IDictionary<string, object> Properties { get; set; }
}

First, we create a simple query that searches for matching products and returns a given number of results:

Prepared Query
public class GetMatchingProductsQuery : IPreparedQuery<Product>
{
    public string MatchClause { get; set; }

    public int Skip { get; set; } = 0;

    public int Take { get; set; } = 15;

    public QueryMetadata Metadata { get; set; };

    public Expression<Func<IFulltextQuery<Product>, IFulltextQuery<Product>>> Query() => q => q.Match(MatchClause).Limit(Skip, Take).Metadata(out Metadata);
}

Note how we're retrieving query metadata by declaring a property on our query class to hold the result. That property will have the result assigned after successful execution of the query.

Executing a Prepared Query
var matchingProductsQuery = new GetMatchingProductsQuery { MatchClause = "My product search" }

using (IFulltextSession fulltextSession = fulltextStore.StartSession())
{
    var result = await fulltextSession.QueryAsync(matchingProductsQuery);
    //matchingProductsQuery.MetaData is now populated with the result
}

As we're not performing a projection of our doucment to another type, we use IPreparedQueryy<TDocument> to implement our query class. There's also IPreparedQuery<TDocument, TResult> which should be used if the document gets projected into another type.

Prepared Future Queries

Like regular queries, prepared queries can be scheduled to be executed in a single round trip to searchd. Both query types can be mixed:

Exectuing a Prepared Query
var matchingProductsQuery = new GetMatchingProductsQuery { MatchClause = "My product search" }

using (IFulltextSession fulltextSession = fulltextStore.StartSession())
{
    var lazyMatchingProductsResult = session.QueryFutureAsync(matchingProductsQuery).ConfigureAwait(false);

    var facets = fulltextSession.Query<Product>().
                                 Match(matchingProductsQuery.MatchClause).
                                 GroupBy(p => p.CategoryId).
                                 Select(p => new 
                                 {   
                                    p.CategoryId,
                                    Count = Projection.Count()
                                 }).
                                 ToFutureListAsync();

   //No query has been executed up to this point

   foreach (result in await lazyMatchingProductsResult.Value) //<-- accessing the result of a query will cause all pending future queries 
   {                                                          //to be executed in one roundtrip to searchd at this point
        //...
   }
}