Working with Solr Using SolrNet

12.06.17   Prasanth Nittala

There may be use cases where you want to integrate with Solr using the SolrNet library instead of going through Sitecore Content Search API.  This post describes how to configure and work with Solr using SolrNet library with code snippets to perform different queries such as FilterQuery Parameter, Facet Parameters, Filter Query Parameters with Values, Facet Pivot Parameters, Grouping Parameters, Date Range Parameters and their corresponding solr Urls. My next post will describe the concepts of analyzers/filters and how to debug the scoring mechanism of Solr.

Configuration

Solr Configuration

Basic request handler configuraton for Search can be done as follows in Solrconfig.xml:

SolrConfig configuration
<requestHandlername="/search"class="solr.SearchHandler">
   <!-- default values for query parameters can be specified, these
         will be overridden by parameters in the request
   -->
   <lstname="defaults">
      <strname="echoParams">explicit</str>
      <intname="rows">10</int>
      <strname="defType">edismax</str>
      <strname="q.op">OR</str>
      <strname="df">description_t</str>
 
       <!-- highlight -->
      <strname="hl">true</str>
      <strname="hl.simple.pre">&lt;b&gt;</str>
      <strname="hl.simple.post">&lt;/b&gt;</str>
      <strname="hl.snippets">1</str>
      <strname="hl.fragsize">200</str>
 
 
      <!-- spellcheck -->
      <strname="spellcheck">true</str>
      <strname="spellcheck.collate">true</str>
      <strname="spellcheck.dictionary">default</str>
      <strname="spellcheck.onlyMorePopular">true</str>
      <strname="spellcheck.count">1</str>
      <strname="spellcheck.alternativeTermCount">1</str>
      <strname="spellcheck.collateExtendedResults">false</str>
      <strname="spellcheck.maxResultsForSuggest">1</str>
 
 
      <!-- facet min count-->
      <strname="facet.mincount">1</str>
   </lst>
 
    <!-- add spell check component to this handler -->
   <arrname="last-components">
      <str>spellcheck</str>
   </arr>
</requestHandler>
 
<!--define Spellcheck component -->
<searchComponentname="spellcheck"class="solr.SpellCheckComponent">
   <lstname="spellchecker">
      <strname="name">default</str>
      <strname="field">spellcheck_field</str>
      <strname="classname">solr.DirectSolrSpellChecker</str>
      <strname="distanceMeasure">internal</str>
      <floatname="accuracy">0.7</float>
      <intname="maxEdits">2</int>
      <intname="minPrefix">1</int>
      <intname="maxInspections">5</int>
      <intname="minQueryLength">4</int>
      <floatname="maxQueryFrequency">0.01</float>
      <floatname="thresholdTokenFrequency">.01</float>
   </lst>
</searchComponent>
 

Schema Configuration

In the fields section, the following fields were configured and corresponding field type "text_general" is configured in the schema.xml.

Schema configuration
<fields>
   <fieldname="car_make"type="string"indexed="true"stored="true"/>
   <fieldname="car_model"type="string"indexed="true"stored="true"/>
   <fieldname="description_t"type="text_general"indexed="true"stored="true"/>
   <fieldname="car_updated_date"type="tdate"indexed="true"stored="true"/>
   <fieldname="spellcheck_field"stored="false"type="text_general"multiValued="true"indexed="true"termVectors="true"/>
   <copyFieldsource="car_model"dest="spellcheck_field"/>
</fields>
 
<types>
   <fieldTypename="text_general"class="solr.TextField"positionIncrementGap="100">
      <analyzertype="index">
         <charFilterclass="solr.HTMLStripCharFilterFactory"/>
         <tokenizerclass="solr.WhitespaceTokenizerFactory"/>
         <filterclass="solr.LowerCaseFilterFactory"/>
         <filterclass="solr.WordDelimiterFilterFactory"splitOnNumerics="1"splitOnCaseChange="1"generateWordParts="1"generateNumberParts="1"catenateWords="1"catenateNumbers="1"catenateAll="1"preserveOriginal="1"/>
      </analyzer>
      <analyzertype="query">
         <charFilterclass="solr.HTMLStripCharFilterFactory"/>
         <tokenizerclass="solr.WhitespaceTokenizerFactory"/>
         <filterclass="solr.LowerCaseFilterFactory"/>
         <filterclass="solr.WordDelimiterFilterFactory"splitOnNumerics="0"splitOnCaseChange="1"generateWordParts="1"generateNumberParts="1"catenateWords="1"catenateNumbers="1"catenateAll="1"preserveOriginal="1"/>
      </analyzer>
   </fieldType>
</types>

 

Queries with SolrNet

Base Search Parameters

BasicSolrResultsRetrieval
QueryOptions searchParameters = new QueryOptions();
AbstractSolrQuery solrQuery = BuildBasicSearchResultsSolrQuery(searchRequest, ref searchParameters);
SolrQueryResults<SearchResult> solrResults = GetSolrOperations<SearchResult>().Query(solrQuery, searchParameters);
string spellCheckString = string.Empty;
if (solrResults != null && solrResults.SpellChecking != null))
   spellCheckString = GetSpellCheck(solrResults).FirstOrDefault();
SearchResult Class
public class SearchRequest
{
   public string SearchText { get; set; }
 
   public string Language { get; set; }
 
   public int PageNo { get; set; }
 
   public int PageSize { get; set; }
 
   public List<string> FacetFields { get; set; }
 
   public Dictionary<string,List<string>> Filters { get; set; }
 
   //asc or desc
   public string Sort { get; set; }
 
   public DateTime? RecordUpdatedFromDate { get; set; }
 
   public DateTime? RecordUpdatedToDate { get; set; }
 
   public SearchType SearchType { get; set; }
}
 
public class SearchResult
{
   [DataMember]
   [SolrField("description_t")]
   public string Description { get; set; }
 
   [DataMember]
   [SolrField("car_make")]
   public string Make { get; set; }
 
   [DataMember]
   [SolrField("car_model")]
   public string Model { get; set; }
 
   [DataMember]
   [SolrField("car_updated_date")]
   public string UpdateDate { get; set; }
}
BuildBasicSearchSolrQuery
privateAbstractSolrQuery BuildBasicSearchResultsSolrQuery(SearchRequest searchRequest,refQueryOptions searchParameters)
{
   AbstractSolrQuery solrQuery =newSolrQuery(searchRequest.SearchText);
 
   searchParameters.ExtraParams =newDictionary<string,string> {
         {"qt","/search"},//querying the "search" request handler configured in solrconfig
         {"qf","car_make car_model description_t"},//query fields are car_name and description_t Solr fields
         {"hl","true"},//enable highlighting
         {"hl.fl","description_t"},//highlighted fields are car_name and description_t
         {"spellcheck.q",searchRequest.SearchText }//to perform spellchecking for the searchtext entered
      };
 
   //working with pagination
   searchParameters.Rows = searchRequest.PageSize;//pagesize is set to 10
   searchParameters.Start = (searchRequest.PageNo - 1) * searchRequest.PageSize;//start will be 0 initially
 
   //sort by ascending or descending on Solr "car_name" field
   if(!string.IsNullOrEmpty(searchRequest.Sort))
   {
      SolrNet.SortOrder[] order =new[] {newSolrNet.SortOrder("car_name", searchRequest.Sort.Equals("asc")?Order.ASC:Order.DESC};//sort by car_name solr field
      searchParameters.OrderBy = order;
   }
 
   searchParameters.SpellCheck =newSpellCheckingParameters() { };
   returnsolrQuery;
}
SpellCheck results
privateIList<string> GetSpellCheck(SolrQueryResults<ContentSearchResult> results)
{
   IList<string> spell =newList<string>();
   if(results !=null)
   {
      SpellCheckResults spellCheckResults = results.SpellChecking;
      foreach(SpellCheckResult resinspellCheckResults)
      {
         foreach(stringsuggestioninres.Suggestions)
         {
            spell.Add(suggestion);
         }
      }
   }
   returnspell;
}

This results in a query similar to what's shown here: http://localhost:8983/solr/car_search_index/search?q=corolla&start=0&rows=10&qf=car_make+car_model+description_t&wt=json&indent=true&qt=/search&hl=true&hl.fl=description_t&spellcheck.q=corolla&spellcheck=true

FilterQuery Parameters

Adding a filter query for querying solr is as follows:

FilterQueries
privatevoidAddLanguageFilterQuery(SearchRequest searchRequest,refQueryOptions searchParameters)
{
   searchParameters.AddFilterQueries(newISolrQuery[] {newSolrQueryByField("car_make","Toyota") });//to get all vehicles with make Toyota
}

This results in appending fq parameter to the request as shown in bold here: http://localhost:8983/solr/car_search_index/search?q=*&start=0&rows=10&qf=car_make+car_model+description_t&wt=json&indent=true&fq=(car_make:(Toyota))

Facet Parameters

Adding Facet Field parameters are as follow:

Facet Parameters
privatevoidCreateFacetParameters(SearchRequest searchRequest,refQueryOptions searchParameters)
{
   FacetParameters fp =newFacetParameters();
   //facet fields for query
   // example searchRequest.FacetFields = {"car_make", "car_model"} Solr Fields in the Solr Document
   foreach(stringfacetFieldNameinsearchRequest.FacetFields)
   {
      fp.Queries.Add(newSolrFacetFieldQuery(facetFieldName));
   }
   searchParameters.Facet = fp;
}

This results in appending facet parameters to the request as shown in bold here: http://localhost:8983/solr/car_search_index/search?q=*&start=0&rows=10&qf=car_make+car_model+description_t&wt=json&indent=true&facet=true&facet.field=car_make&facet.field=car_model

FilterQuery to filter based on facet values:

This is useful in cases where you are filtering the results based on facets. For example, 

FacetValue FilterQuery Parameters
privatevoidCreateFilterQueryBasedonFacetValue(refQueryOptions option,stringfilterFieldName, IList<string> filterFieldValues)
{
   List<ISolrQuery> sqList =newList<ISolrQuery>();
   foreach(stringfilterFieldValueinfilterFieldValues)
   {
      if(!string.IsNullOrWhiteSpace(filterFieldValue))
      {
         var fieldQuery =newSolrQueryByField(filterFieldName, filterFieldValue);
         sqList.Add(fieldQuery);
      }
   }
 
   if(sqList.Count > 0)
   {
      //using OR criteria among the filter values
      option.FilterQueries.Add(newSolrMultipleCriteriaQuery(sqList, SolrMultipleCriteriaQuery.Operator.OR));
   }
}

Assuming we are filtering by "Corolla" OR "Camry" models, then the filterquery request is as shown in bold here: http://localhost:8983/solr/car_search_index/search?q=*&start=0&rows=10&qf=car_make+car_model+description_t&wt=json&indent=true&fq=(car_model:(Corolla)+OR+car_model:(Camry))

Facet Pivot Parameters

FacetPivot Parameters
privatevoidCreateFacetPivotParameters(SearchRequest searchRequest,refQueryOptions searchParameters)
{
   FacetParameters fp =newFacetParameters();
 
 
   //assuming car_make, car_model are two solrfields in the solr document on which we are pivoting
   List<string> pivots =newList<string>
   {
      "car_make",
      "car_model"
   };
 
   var facetPivotQuery =newSolrFacetPivotQuery()
   {
      Fields = pivots,
      MinCount = 1
   };
 
 
   fp.Queries.Add(facetPivotQuery);
   fp.Sort =false;
   searchParameters.Facet = fp;
}

This results in appending facet parameters to the request as shown in bold here: http://localhost:8983/solr/car_search_index/search?q=*&start=0&rows=10&qf=car_make+car_model+description_t&wt=json&indent=true&facet=true&facet.pivot=car_make,car_model&facet.pivot.mincount=1&facet.sort=false

Grouping Parameters

Adding grouping parameters to Solr Query is as follows:

Grouping Parameters
privatevoidAddGroupingParametersByContentTypeId(SearchRequest searchRequest,refQueryOptions searchParameters)
{
   var gp =newGroupingParameters{
         Fields =newstring[] {"car_model"},//grouping by solr "car_model" field,
         Limit = 3,//limit 3 results per group
      };
 
   searchParameters.Rows = 10;//return at max 10 groups, as this can limit the number of groups returned
   searchParameters.Grouping = gp;
}

This results in appending grouping parameters to the request as shown in bold here: http://localhost:8983/solr/car_search_index/search?q=*&start=0&rows=10&qf=car_make+car_model+description_t&wt=json&indent=true&group=true&group.field=car_model&group.limit=3

Date Range Query Parameters

Date Range Parameters
privatevoidAddDateRangeFilters(refQueryOptions option, SearchRequest searchRequest)
{
   var dateQuery =newSolrQueryByRange<DateTime?>("car_updated_date", searchRequest.RecordUpdatedFromDate, searchRequest.RecordUpdatedToDate);
   option.FilterQueries.Add(dateQuery);
}

Assuming you are searching between RecordUpdatedFromDate as 06/13/2017 and RecordUpdatedToDate as 07/13/2017 (1 month), this results in appending filter query parameters to the request as shown in bold here: http://localhost:8983/solr/car_search_index/search?q=*&start=0&rows=10&qf=car_make+car_model+description_t&wt=json&indent=true&fq=car_updated_date:[2017-06-13T00:00:00Z+TO+2017-07-13T00:00:00Z]