Tags

, , , , ,

The module Sitecore Ecommerce Services offers a quite brilliant Search Framework. With this framework it is possible to build up a Query with a strongly typed model. This Query model could then be passed to an implementation of “ISearchProvider”, which uses a Lucene search or a Sitecore query search for example, and it returns a collection of items. This is quite brilliant, because it offers an abstraction layer for the several different ways to search the Sitecore database. This greatly enhances loosely coupled application design, because it enabled several (filter) controls to add filters to the Query model, without having to know which implementation is used to search the database.

The module harbors three implementation of searchproviders: Sitecore Query, Fast Query and Lucene. In this article I will show how this framework could be extended with a SOLR (Sitecore Search Solution) implementation. SOLR is a much more powerful searchprovider, because it enables facetted search. By adding an abstraction layer to the Sitecore Search Solution implementation, one also upholds the principles of modular, loosely coupled application design.

In the namespace “Sitecore.Ecommerce.Search” of Sitecore Ecommerce Services (version 2.0) we find number of public classes: models, querybuilders and searchproviders. The models consist of a Query class, and a number of classes that inherit from QueryElement: Condition, AttributeQuery and FieldQuery class. The Query object is built up by adding these QueryElements.

The SolrQueryBuilder class must contain a public method BuildResultQuery, which returns a SitecoreSearchSolution.Web.Entities.SearchQueryFE object. This object will be used only internally in the SolrSearchProvider, so there is no need to create an abstraction layer around this. Inside this method, a AlphaSolutions.Inveniens.Framework.SOLR.Model.Query.StringQueryFilter object is created, and added to the SearchQueryFE.Filters. This StringQueryFilter is built up with the recursive “AppendQueryElement” method.

internal class SolrQueryBuilder
{
	public SearchQueryFE BuildResultQuery(Model.Query query)
	{
		SearchQueryFE solrQuery = new SearchQueryFE();

		StringBuilder filterQueryBuilder = new StringBuilder();

		AppendQueryElement(filterQueryBuilder, query.GetFirstElement());

		StringQueryFilter stringQueryFilter = new StringQueryFilter(filterQueryBuilder.ToString());

		if (solrQuery.Filters == null)
		{
			solrQuery.Filters = new List();
		}
		solrQuery.Filters.Add(stringQueryFilter.ToHttpQueryStringPart());

		return solrQuery;
	}
}

In the “AppendQueryElement” method each QueryElement in the Query is parsed as a string and added to the StringBuilder for the StringQueryFilter. The parsing for an AttributeQuery and a FieldQuery is the same for the SOLR implementation. We use AttributeQuery in this example.

if (queryElement is AttributeQuery)
{
	FieldedQuery fieldedQuery = new FieldedQuery(
		attributeQuery.Key,
		GetMatchVariantSolr(attributeQuery.MatchVariant),
		attributeQuery.Value);

	filterQueryBuilder.Append(fieldedQuery.ToHttpQueryStringPart());
}

For the correct parsing of the QueryElement to string, we use the class AlphaSolutions.Inveniens.Framework.SOLR.Model.Query.FieldedQuery. The Key and Value of the QueryElement could be passed directly in its constructor, but the Sitecore.Ecommerce.Search.MatchVariant has to be mapped to a AlphaSolutions.Inveniens.Framework.SOLR.Model.Query.FieldOperator first.

private FieldOperator GetMatchVariantSolr(MatchVariant matchVariant)
{
	switch (matchVariant)
	{
		case MatchVariant.Equals:
			return FieldOperator.Equals;
		case MatchVariant.NotEquals:
			return FieldOperator.NotEquals;
		case MatchVariant.GreaterThanOrEqual:
			return FieldOperator.GreaterThanOrEqual;
		case MatchVariant.GreaterThan:
			return FieldOperator.GreaterThan;
		case MatchVariant.LessThanOrEqual:
			return FieldOperator.LessThanOrEqual;
		case MatchVariant.LessThan:
			return FieldOperator.LessThanOrEqual;
		default:
			return FieldOperator.Equals;
	}
}

Then these AttributeQueries and FieldQueries have to be conjoined by what Sitecore Ecommerce Services calls a “Condition”. This is really a conjunction, as it conjoins the subqueries by an “AND” or “OR” logical conjunction. In a SOLR Query this is represented by a simple “and” or “or” string, so we need to add the following to the SolrQueryBuilder:

else if (queryElement is Conjunction)
{
	Condition conjunction = queryElement as Condition;

	switch (conjunction.QueryCondition)
	{
	case QueryConjunction.And:
		filterQueryBuilder.Append(" AND ");
		break;
	case QueryConjunction.Or:
		filterQueryBuilder.Append(" OR ");
		break;
	default:
		break;
	}
}

Now we have handled every type of QueryElement, and the entire Query could be parsed to an implementation of ISearchProvider. The interface forces our class to implement the methods:

IEnumerable Search(Query query);
IEnumerable Search(Query query, Database database);

Inside this SearchProvider, we call the SolrQueryBuilder.BuildResultQuery() method, which returns a SearchQueryFE object. This object is simply passed to the method SitecoreSearchSolution.Web.Services.SearchService.GetSearch() method. This method returns a SearchResultFE object, and the circle is completed. The abstract query framework of Sitecore Ecommerce Services is extended with a SOLR implementation.

Advertisements