Sitecore Search and Predicate Builder Queries

August 2, 2017

Blog | Development | Sitecore Search and Predicate Builder Queries
Sitecore Search Predicate Builder Queries

Out of the box, Sitecore has strong search capabilities with querying options Sitecore Search, Fast, and Index Based (Lucene, Solr, etc) each with their own benefits.  When it comes to the last of those search types, Index Based searches, my favorite way to implement (and in my opinion, the easiest to follow logically) is thru the use of the PredicateBuilder.  I will start with some of the basics.

Base Contains Search

Just for the sake of example, I will include the class below:

public class ContentSearchResultItem : Sitecore.ContentSearch.SearchTypes.SearchResultItem
 {
     [DataMember]
     [IndexField("_uniqueid")]
     public string UniqueId { get; set; }

     [DataMember]
     [IndexField("tag")]
     public string Tag { get; set; }

     [DataMember]
     [IndexField("keywords_terms")]
     public string KeywordTerms { get; set; }

     [DataMember]
     [IndexField("page_title")]
     public string PageTitle { get; set; }

     [DataMember]
     [IndexField("body_content")]
     public string BodyContent { get; set; }
 }

This class extends the Sitecore.ContentSearch.SearchTypes.SearchResultItem type, and is used to map the Indexed results and fields back to a proper type.

The snippet below is a basic Contains query:

using (var context = index.CreateSearchContext())
 {
     //baseQuery, set to True, so if we have no searchTerm, all items are returned.
     var baseQuery = PredicateBuilder.True<ContentSearchResultItem>();

    var containsQuery = PredicateBuilder.True<ContentSearchResultItem>();
     if (!searchTerm.IsNullOrEmpty())
     {
         //here we are breaking up all the terms in the searchTerm, reults may vary
         //depending on what the indexType is on these fields in your index config (indexType="TOKENIZED")
         var tokens = searchTerm.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
         foreach (var token in tokens)
         {
             var currentToken = token;
             containsQuery = containsQuery.Or(item => item.Name.Contains(currentToken));
             containsQuery = containsQuery.Or(item => item.KeywordTerms.Contains(currentToken));
             containsQuery = containsQuery.Or(item => item.PageTitle.Contains(currentToken));
             containsQuery = containsQuery.Or(item => item.BodyContent.Contains(currentToken));
         }
     }
     //join the baseQuery and the containsQuery
     baseQuery = baseQuery.And(containsQuery);
     var queryRunner = context.GetQueryable().Where(baseQuery);
     var results = queryRunner.GetResults();
     var pagedResults = fullResults ? results.Hits : results.Hits.ToPagedList(currentPage, resultsPerPage).ToList();
     var totalResults = results.TotalSearchResults;
     //now that we have the results for our current page, get the proper items
     var resultItems = pagedResults.Select(x => x.Document.GetItem());
 }

The above snippet will simply query the index for the terms (broken up across different fields) and return the most relevant results.

Filter by Templates

For some, the basic Contains may be all that is needed, but if your instance has multiple types of content, some you want to be returned in the search results and some not, you may want to filter by the actual content type. Adding the ability to filter by templates type is as easy as adding:

 var supportedTemplates = new List { BlogPage.TemplateId, ArticlePage.TemplateId };
 //using false, because we do not want any results returned if it does not match any of the templates
 var templateQuery = PredicateBuilder.False<ContentSearchResultItem>();
 templateQuery = templateQuery.Or(x => supportedTemplates.Contains(x.TemplateId));
 ...
 //add before the queryRunner
 baseQuery = baseQuery.And(templateQuery);

Filter by Tag

Similar to the snippet above, we will be adding another query to filter the results by a field called Tag.  For this example, in the Content Editor, a Tag is associated with a page thru one of the list type fields:

 var filterQuery = PredicateBuilder.True<ContentSearchResultItem>();
 if (!filter.IsNullOrEmpty())
 {
     filterQuery = filterQuery.Or(x => x.Tag.Contains(filter));
 }
 ...
 //add before the queryRunner
 baseQuery = baseQuery.And(filterQuery);

Conclusion

If you were to compile all of the snippets above, you would have a working search example that would search by term, exclude non-searchable templates,  and filter results by associated tags. Thru the use of the PredicateBuilder.True|False notation, the inclusion or exclusion of results is easy to follow and the logic is easy to comprehend.  This can make handling large, complex index queries much easier to manage and I see PredicateBuilder as one of the biggest advantages of using Sitecore’s index based search.

Steve VandenBush

Technical Lead
Tags
  • Best Practices
  • Sitecore
  • Web Development

Recent Work

Check out what else we've been working on