LINQ to Sitecore
LINQ to Sitecore provides access to search the indexes, using standard LINQ queries.
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.
Sitecore supports two search providers: Lucene and Solr. The LINQ layer is an abstract layer that converts common queries to something that these search providers understand.
For example, the LINQ layer resolves a query like this:
var query = context.GetQueryable<Product>.Where(item => item.Name == “Sample Item”)
to something that Solr or Lucene 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:
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. For example, for Lucene it is converted to:
_name:sitecore
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:
using (var context = ContentSearchManager.GetIndex(item).CreateSearchContext()) { IQueryable<SearchResultItem> searchQuery = context.GetQueryable<SearchResultItem>().Where(item => item[“_name”] == “Sitecore”) }
This topic describes:
The LINQ layer does not implement all IQueryable
methods. The following methods have been implemented:
Sort by standard string, integer, float, or any type that implements
IComparable
All
Any
Between (with an extra overload for including or excluding the start and end ranges)
Boost (makes this part of the query more important than the other parts)
Cast (you can use this instead of Select)
Contains
Count
ElementAt
EndsWith
Equal
Facets (an extension that fetches the facets of predicates)
First
FirstOrDefault
Join
Last
LastOrDefault
Min
Max
Match (an extension for running regular expression queries)
OrderBy
OrderByDescending
Select
Single
SingleOrDefault
Skip
Take
ToList()
ToLookUp()
ToDictionary()
Page (an extension that does Skip and Take automatically for you)
StartsWith
Not supported IQueryable methods
These methods are not supported:
GroupBy
GroupByJoin
Intersect
Sum
Average
Concat
TakeWhile
SkipWhile
Reverse
Union
If these methods are called, a NotSupportedException
or InvalidOperationException
exception is thrown at runtime.
This table shows how the LINQ layer and Lucene correspond:
Lucene syntax | LINQ to Sitecore | |
---|---|---|
Fields |
|
|
WildCard |
|
|
Prefix |
|
|
Fuzzy |
|
|
Proximity |
|
|
Inclusive Range |
|
|
Exclusive Range |
|
|
Boosting |
|
|
Boolean Or |
|
|
Boolean And |
|
|
Boolean Not |
|
|
Grouping | ( |
|
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:
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
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
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
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
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
results = queryable.Where(d => !d.Template.Contains("Hello:));
string.CompareTo
results = queryable.Where(d => !d.Name.CompareTo("Hello") == 1);
Equal
results = queryable.Where(d => d.Id.Equal(4));
Matches
results = queryable.Where(i => i.Template.Matches("^.*$"));
MatchWildcard
results = queryable.Where(i => i.Template.Where(i => i.Template.MatchWildcard("H?li*m")));
Like
results = queryable.Where(i => i.Template.Like("Citecoar"));
string.StartsWith
results = queryable.Where(d => !d.Name.StartsWith("Hello"));
string.EndsWith
results = queryable.Where(d => !d.Name.EndsWith("Hello"));
GetResults
results = queryable.GetResults().Hits.Where(i => i.Document.Name.Contains("o")).Where(hit => hit.Score > 0.6);
GetFacets
results = queryable.Where(d => d.Id > 0).FacetOn(d => d.Template, 0).GetFacets();
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".
Depending on the search provider being used, the indexed and stored data in the index might not be the native types for the value. For Lucene, everything is stored and indexed as strings.
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:
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; } } }