Search indexes using LINQ to Sitecore

Current version: 10.0

LINQ to Sitecore provides access to search the indexes, using standard LINQ queries in the same way that other LINQ providers, such as LINQ to SQL and LINQ to Objects, work. It uses the standard IQueryable<T> interface and supports most of the available operations. For a general introduction to LINQ, see http://msdn.microsoft.com/en-us/library/vstudio/bb397926.aspx.

The LINQ layer is an abstract layer that converts common queries to something a search provider understands.

For example, the LINQ layer resolves a query like this:

RequestResponse
var query = context.GetQueryable<Product>.Where(item => item.Name == “Sample Item”) 

to something that the search provider understands. If you implement a new search provider, this provider can also understand the query.

The LINQ layer is used internally by Sitecore but developers can also use it. You can use this layer in sublayouts.

To start a search, you set up a search context:

RequestResponse
using (var context = ContentSearchManager.GetIndex(item).CreateSearchContext())
{
    IQueryable<SearchResultItem> searchQuery = context.GetQueryable<SearchResultItem>().Where(item => item.Name ==Sitecore”)
}

The LINQ layer converts the query to something your provider understands.

This returns the results of a query on your search index and returns it as a SearchResultItem type. You can also use the indexer to run queries:

RequestResponse
using (var context = ContentSearchManager.GetIndex(item).CreateSearchContext())
{
    IQueryable<SearchResultItem> searchQuery = context.GetQueryable<SearchResultItem>().Where(item => item[“_name”] ==Sitecore”)
}

This topic describes:

Supported IQueryable methods

The LINQ layer does not implement all IQueryable methods.

These methods are supported:

  • Sort by standard string, integer, float, or any type that implements IComparable

  • All

  • And

  • Any

  • Between

  • Boost

  • Cast

  • Constant

  • Contains

  • Count

  • ElementAt

  • EndsWith

  • Equal

  • FacetOn

  • FacetPivotOn

  • Field

  • Filter

  • First

  • GetFacets

  • GetResults

  • GreaterThan

  • GreaterThanOrEqual

  • GroupJoin

  • InContext

  • Join

  • Last

  • LessThan

  • LessThanOrEqual

  • Like

  • MatchAll

  • Matches

  • MatchNone

  • Min

  • Max

  • Negate

  • Not

  • NotEqual

  • Or

  • OrderBy

  • OrderByDistance

  • Select

  • SelectMany

  • Single

  • Skip

  • StartsWith

  • Take

  • Union

  • Where

  • WildcardMatch

  • WithinRadius

  • ToList()

  • ToLookUp()

  • ToDictionary()

  • StartsWith

Not supported IQueryable methods

These methods are not supported:

  • Average

  • Concat

  • Facets

  • FirstOrDefault

  • GroupBy

  • Intersect

  • LastOrDefault

  • Match

  • OrderByDescending

  • Reverse

  • SingleOrDefault

  • SkipWhile

  • Sum

  • TakeWhile

If these methods are called, a NotSupportedException or InvalidOperationException exception is thrown at runtime.

LINQ to Sitecore syntax

This table shows how the LINQ layer and Solr correspond:

Solr syntax

LINQ to Sitecore

Fields

_text:”go”

.Where(c => c.Text == "go") 

- or:

.Where(c => c.Text.Equals(“go”))

WildCard

_text:*amber*

.Where(c => c.Text.Contains ("amber"))

Prefix

_text: amber*

.Where(c => c.ContactName.StartsWith("amber"))

Fuzzy

_text:roam~

_text:roam~0.8

.Where(c => c.Text.Like("roam"))

.Where(c => c.Text.Like("roam", 0.8))

Proximity

_text:"jakarta apache"~10

.Where(c => c.Text.Like("jakarta apache", 10))

Inclusive Range

__smallupdateddate:[20140101 TO 20150101]

.Where(c => c.Updated.Between(new DateTime(2014, 1, 1), new DateTime(2015, 1, 1), Inclusion.Both)

Exclusive Range

_title:{Aida TO Carmen}

.Where(c => c.Title.Between("Aida", "Carmen", Inclusion.None))

Boosting

_name:jakarta^4 apache

.Where(c => c.Name.Equals("jakarta").Boost(4) || c.Name.Equals("apache"))

Boolean Or

_name:"jakarta apache" OR _name:jakarta

.Where c.Name.Equals("jakarta apache") || c.Name.Equals("jakarta")

Boolean And

_name:"jakarta apache" AND _text:"go"

.Where c.Name.Equals("jakarta apache") && c.Text.Equals("go")

Boolean Not

_name:"jakarta apache" NOT _name:"Apache Solr"

.Where c.Name.Equals("jakarta apache") && !c.Name.Equals("Apache Solr")

Grouping

_name:(jakarta OR apache) AND _text:go

where (c.Name == "jakarta" || c.Name == "apache") && (c.Text == "go")

IQueryable extensions

The LINQ layer provides extra methods that extend the standard IQueryable interface. You must declare the Sitecore.ContentSearch.Linq namespace to use them.

Filtering

Filtering is similar to using Where to restrict the result list. When you use Filter, the scoring/ranking of the search hits is not influenced by the filters, and filters can be cached to optimize search performance.

For example:

RequestResponse
results = queryable.Filter(d => d.Id > 4 && d.Id < 8);
Note

To avoid influencing the ranking of the search results, use Filter when applying restrictions to search queries in the GetGlobalFilters pipeline.

Facets

Simple faceting

RequestResponse
var results = queryable.FacetOn(d => d.Name); 
var facets = results.GetFacets(); 
foreach (var category in facets.Categories) { 
    Console.WriteLine(category.Name); 
    foreach (var facetValue in category.Values) { 
        Console.WriteLine("{0}: {1}", facetValue.Name, facetValue.Aggregate); 
    } 
}

Pivot faceting

RequestResponse
var results = queryable.FacetPivotOn(p => p.FacetOn(d => d.Name).FacetOn(d => d.Year)); 
var facets = results.GetFacets(); 
foreach (var category in facets.Categories) { 
    Console.WriteLine(category.Name); 
    foreach (var facetValue in category.Values) { 
        Console.WriteLine("{0}: {1}", facetValue.Name, facetValue.Aggregate); 
    } 
}

Boosting

RequestResponse
queryable.Where(it => (it.TemplateName == "Sample Item").Boost(50) || it.TemplateName=="Folder");
queryable.Where(it => (it.Paths.Contains(new ID("{0DE95AE4-41AB-4D01-9EB0-67441B7C2450}")).Boost(3) || it.TemplateName=="Folder") );

Other

Between

RequestResponse
results = queryable.Where(item => item.Price.Between(50.0f, 400.0f, Inclusion.Both));
results = queryable.Where(item => item.Price.Between(2.0f, 12.0f, Inclusion.Both) || item.Price.Between(80.0f, 400.0f, Inclusion.Both)); 
results = queryable.Where(d => d.Date.Between(new DateTime(2004, 12, 31), DateTime.Now, Inclusion.Both)); 
results = queryable.Where(d => d.Id.Between(1, 4, Inclusion.Both)); 
results = queryable.Where(d => d.Id.Between(1, 4, Inclusion.Lower)); 
results = queryable.Where(d => d.Id.Between(1, 4, Inclusion.Upper)); 
results = queryable.Where(d => d.Id.Between(1, 4, Inclusion.None)); 

string.Contains

RequestResponse
results = queryable.Where(d => !d.Template.Contains("Hello:));

string.CompareTo

RequestResponse
results = queryable.Where(d => !d.Name.CompareTo("Hello") == 1);

Equal

RequestResponse
results = queryable.Where(d => d.Id.Equal(4));

Matches

RequestResponse
results = queryable.Where(i => i.Template.Matches("^.*$")); 

MatchWildcard

RequestResponse
results = queryable.Where(i => i.Template.Where(i => i.Template.MatchWildcard("H?li*m")));

Like

RequestResponse
results = queryable.Where(i => i.Template.Like("Citecoar")); 

string.StartsWith

RequestResponse
results = queryable.Where(d => !d.Name.StartsWith("Hello")); 

string.EndsWith

RequestResponse
results = queryable.Where(d => !d.Name.EndsWith("Hello"));

GetResults

RequestResponse
results = queryable.GetResults().Hits.Where(i => i.Document.Name.Contains("o")).Where(hit => hit.Score > 0.6); 

GetFacets

RequestResponse
results = queryable.Where(d => d.Id > 0).FacetOn(d => d.Template, 0).GetFacets();

Custom search type/object mapping

Because the LINQ layer uses the generic IQueryable<T> interface to expose the search indexes, you can use custom classes or POCO classes to describe the information in the indexes.

To implement custom search types, such a class must:

  • Have an empty constructor.

  • Expose public properties with getters and setters and/or a public indexer to hold the search result data.

The LINQ provider automatically maps document fields in the index to properties on the custom search type by the names of the properties. Properties or fields from the index that have not been matched together by name are skipped during the object creation/mapping.

It is also possible to map properties that do not match to fields in the index by decorating the properties with the IndexField attribute. You can use this, for example, to expose special Sitecore fields such as _name as a property called Name. A different use case is field names with spaces, because they cannot be mapped directly to a property by name.

Furthermore, you can implement an indexer that is populated with the field name as key and the value for each field in the index document. There is also an ObjectIndexerKey that you can use to wrap indexers as different types. This is useful if you only have the string version of a property name but need to use it as an indexer for a property type, where you most often need an int.

The process of supplying a custom type and let Sitecore map from index fields to the properties of the item is also known as "hydration".

Supported types

The following types are supported for automatic type conversion when mapping index document fields to properties:

  • .NET built-in integral types

  • .NET built-in floating-point types

  • Boolean

  • String

  • DateTime

  • GUID

  • Sitecore ID

  • Sitecore ShortID

  • Sitecore ItemUri

  • IEnumerable<T>

  • DateTimeOffset

  • Language

  • Version

  • Database

  • CultureInfo

  • TimeSpan

Custom search type example

The following is a short example of how you implement a custom search type:

RequestResponse
public class MySearchResultItem 
{
    // Fields 
    private readonly Dictionary<string, stringfields = new Dictionary<string, string>(); 
    // Properties 
    // Will match the _name field in the index 
    [IndexField("_name")] 
    public string Name { get; set; } 
    // Will match the myid field in the index 
    public Guid MyId { get; set; } 
    public int MyNumber { get; set; } 
    public float MyFloatingPointNumber { get; set; } 
    public double MyOtherFloatingPointNumber { get; set;} 
    public DateTime MyDate { get; set; } 
    public ShortID MyShortID { get; set; } 
    public ID SitecoreID { get; set; } 
    // Will be set with key and value for each field in the index document 
    public string this[string key]
    { 
        get 
        { 
            return this.fields[key.ToLowerInvariant()]; 
        } 
        set 
        { 
            this.fields[key.ToLowerInvariant()] = value; 
        } 
    } 
}

Do you have some feedback for us?

If you have suggestions for improving this article,