How to Use Sitecore’s LINQ to Provider for Search-Based Programming

High-quality search solutions. Would you believe that coding with those four words in mind can dramatically improve product performance and influence the final experience a visitor has within your site?

It’s true. Developers are now looking for ways to create flexible, performance-friendly code and search-based solutions that a native provider API cannot necessarily deliver. And with Sitecore’s LINQ to Provider, there’s a clear solution for search-based programming that’s both flexible and efficient.

In my experience, using a search-based approach instead of native APIs is a great way to improve the experience users have with your Sitecore application, by delivering a solution with increased performance and usability. In this blog, I’ll tell you how to get started with LINQ to Provider, the search API piece of LINQ to Sitecore that allows you to easily develop search-centered solutions in Sitecore.

How Does LINQ to Provider Work?

LINQ to Provider uses a LINQ query syntax to abstract your code and focus from the provider-specific query parser. Developers may prefer this because they can write search code for a Lucene-based search project and move that code to a SOLR-based project with little modification. Additionally, Sitecore built upon the existing methods of the IQueryable interface, and also provided a few extensions, to make it possible for anyone who has worked with LINQ to write search code.

Because LINQ to Provider is a newer approach for search solutions in Sitecore-powered applications, I’m going to provide insight on it from a SOLR implementation that I hope will benefit those looking to leverage it.

Basic Methods

LINQ to Provider implements many of the methods already available in the IQueryable interface and includes extensions to simplify functionality. A listing of the implemented IQueryable methods is available in the “Developer’s Guide to Item Buckets and Search v75” available from the Sitecore SDN, and while the document title might not indicate complete relevance to using and working with the API, it’s worth reading for some additional information about LINQ to Provider.

Some of the methods in the list are more important or notable than others, but the commentary below will help to define and clarify their use:

    • Where: This is the primary method of searching for the occurrence of terms or phrases. Using Where translates to a standard query “q” for SOLR and calculates the rank, or score, of each result. For example: Query.Where(i => i.Title == “Test”).
    • Filter: This is best used for exact matches, or category & tag style queries, aka filtering. Filter translates to a “fq” parameter for SOLR, does not affect result rank, and each filter is cached in SOLR’s filterCache. For example: Query.Filter(i => i.Category == “Testing”).
    • Equals: This creates a text match query and wraps your string inside quotes when multiple terms are used. For example: Item.Name.Equals(“Sitecore”) generates ‘_name:Sitecore’.
    • Contains: This translates to a wildcard match for SOLR and wraps your string with asterisks whether it’s a single term or phrase. For example: Item.Name.Contains(“Sitecore”) generates ‘_name:*Sitecore*’.
    • Page: This allows you to specify the number of results and the starting index of those results. It’s a shortcut equivalent of using LINQ Skip and Take. For example: Query.Page(currentPage, pageSize).
    • Boost: This extension can be applied to the string for your search value(s) to give a multiplier of importance to affect score based on that value. For example: Item.Name.Contains(“Sitecore”.Boost(2)).
    • FacetOn: This tells SOLR what fields to provide facets for. This does not actually restrict the results based on a facet value, as you will do that with a Where, or, preferably, a Filter query. For example: Query.FacetOn(i => i.Title) or Query.FacetOn(i => i[“Title”]).
    • GetResults: It’s kind of a given, but this one actually executes the query against SOLR and returns the results. It returns a SearchResults object with three properties:
        • Hits: The number of items pertaining to your current result set. This is different from TotalSearchResults as it only pertains to your current page. If your query returns 235 items and your page size is 20, this will be 20.
        • TotalSearchResults: The total number of matching results across all pages. If your query returns 235 items and your page size is 20, this will be 235.
        • Facets: This is a FacetResults object containing all the facet categories based on your FacetOn values. Each Category object includes a Name and Values property.
    • GetFacets: This returns only the facets without the results. This differs from the GetResults().Facets collection, which returns the complete facet list from the query.

Putting It To Use

Talking about methods, functionality, and APIs can only help someone’s understanding so far, so I have assembled the following code samples to provide guidance on starting with LINQ to Sitecore.

Step 1. Create your context
The first step is creating the search context using the CreateSearchContext method of the ContentSearchManager. You must specify the index to search, and this can be done in a couple of ways.

The first approach uses the GetIndex method to reference the search index using a string parameter. With this approach, you’ll always be querying the specified index no matter what database you’re browsing in Sitecore. This means that whether you’re logged in to Sitecore browsing the Master database, or Web, the index you query will always be “web” if that’s the string you provide:

using (var context = Sitecore.ContentSearch.ContentSearchManager.GetIndex(“sitecore_web_index”).CreateSearchContext()) { [search code here] }

The second approach uses an Item to reference the search index. This method is useful if you want the query to be dynamic based on the database you’re browsing. This means you’ll query the Master index if you’re logged in to Sitecore browsing the Master DB, but will query Web if you aren’t logged in.

using (var context = Sitecore.ContentSearch.ContentSearchManager.CreateSearchContext(new Sitecore.ContentSearch.SitecoreIndexableItem(Sitecore.Context.Item))) { [search code here] }

Step 2. Build your query
The query is built inside the “using” statement from the search context object, and defines the result item type. All search expressions are created on the search query using a LINQ syntax. The following example searches for items with “search” in the Name, existing in English:

var query = context.GetQueryable<SearchResultItem>().Where(i => i.Name.Contains(“search”)).Filter(i => i.Language == “en”);

Step 3. Set your paging and order
By default, Sitecore specifies a “rows” parameter of 500, setting the max results per page of results as 500. This means you’ll return up to 500 items at a time if you don’t specify paging parameters. Use the Page extension to specify the start page and per-page result count. It’s also worth noting that paging should be done on the expression and not after getting the results. Adding the paging parameters to the query offloads pagination to SOLR and improves performance. The example below starts with page 1 and returns a max of 20 items per page:

query = query.Page(1, 20);

Step 4. Get and display results
Up to this point, the code above has simply built a query expression. Until GetResults is called, SOLR is not queried. Once GetResults is called, you’re working with the SearchResults<T> object, not the query. To access the actual results, you’ll reference the Hits property of the SearchResults, and the TotalSearchResults property will provide the total count of results. This code gets the results and DataBinds to a repeater:

var results = query.GetResults();
if (results.Hits.Count() > 0) {
litSearchResults.Text = results.TotalSearchResults.ToString();
rptSearchResults.DataSource = results.Hits;
rptSearchResults.DataBind();
}

Putting It All Together

using (var context = Sitecore.ContentSearch.ContentSearchManager.CreateSearchContext(new Sitecore.ContentSearch.SitecoreIndexableItem(Sitecore.Context.Item))) {
var query = context.GetQueryable<SearchResultItem>().Where(i => i.Name.Contains(“search”)).Filter(i => i.Language == “en”);
query = query.Page(1, 20);
var results = query.GetResults();
if (results.Hits.Count() > 0) {
litSearchResults.Text = results.TotalSearchResults.ToString();
rptSearchResults.DataSource = results.Hits;
rptSearchResults.DataBind();
}
}

While there are certainly more complex techniques and topics involving Sitecore’s ContentSearch, this information and example is designed to be enough to provide a general understanding, and some context, of Sitecore’s LINQ to Provider approach. For more insight into the topic, the Sitecore Dev Team wrote an excellent blog that’s focused more on PredicateBuilder than LINQ to Provider, but still provides some insight into Sitecore’s vision for LINQ to Provider.

Have you used LINQ to Provider? I’d love to hear about your experience!