Sitecore Content Search - Resolving TreeList Field Values and Filtering Index Results

LINQ, Lists, Context, SearchResultItem, Guids, and PredicateBuilder Galore!

When querying a Sitecore tree, it's a best practice to leverage indexes to avoid expensive operations via direct tree traversals.

Real Life Example of Querying the Index and Filtering on the Results

In this example, we're working within a web API that derives Sitecore context using the target host name. Let's say we have a repository of articles. Articles can have multiple authors. We want to query the repository with filters for desired authors.

The technical challenge in this case is that we need to check if any value in the supplied author filter list exists within the article authors list, whose values are stored in a TreeList field. Pulling indexed TreeList values can cause unexpected issues without incorporating all of the features below.

We will also perform a check on where items exist in the tree in order to scope them properly (in this case to filter out the Articles standard value item) as well as an OrderBy on the results.

Important notes when working with indexes:

Important Notes About Index Querying

  • Field names are stored as underscore separated words in lowercase (example: "Article Authors" is stored as "article_authors").
  • There are limitations on what types of operations you can perform in the context of a plain Linq query. For example, operations such as Any() are not supported. To perform this function and other similar functions you must .ToList() your search results. The reason this works is because it executes the query and loads it into memory, and Linq-to-Objects have more methods available than Linq-to-SQL. See Stack Overflow for more information.
  • Some function calls in the Linq query require the creation of a model that "wraps", or inherits from, SearchResultItem.
  • If you are using a different index provider for your local environment vs. an upstream environment, be sure to throughouly test in your upstream environment to verify that all index queries perform as expected. Most developers use Solr in local environments while upstream Azure environments often use Azure Search.

Let's dive into the code.

The Code

using (GetSiteContext())
	// Scope by path (take multi sites into account if necessary) and order the results by most recent 
	// Make note of the x.ArticleDate
	IQueryable query = context.GetQueryable()
		.Where(x => x.TemplateId == _articleTemplateId &&
		.OrderByDescending(x => x.ArticleDate);

	List results = query.ToList();
	// GetAuthorIdFilters explodes a pipe delimited list of author ids and returns a list of Guids
	List authorIdFilters = GetAuthorIdFilters(desiredAuthorIds); 
	// See if any of the filterable guids exist in the Article Authors field 
	results = results.Where(i => authorIdFilters.Any(authorId => i.ArticleAuthors.Contains(authorId))).ToList();
	// Get the items only after the results have been filtered
	List articleItems = results.Select(x => new ArticleSearchItem(x.GetItem())).ToList();


// ..... 

public class ArticleSearchItem : SearchResultItem
	// These properties map the index fields to statically typed fields that, if set up correctly, return the content in a more usable format 
	// In order to pull IDs from a TreeList field in a usable format, we want to map it as a List data type otherwise you may run into null reference exceptions because this field value will be null
	public List ArticleAuthors { get; set; }
	// When using the [IndexField] attribute the field name can be with or without spaces
	public DateTime ArticleDate { get; set; }

The code above serves as a small introduction to working with indexes. Before tackling complex features, be sure to also explore the power that PredicateBuilder offers within the ContentSearch API.

All the best,