Sitecore 7 Linq to Sitecore Simplified Part 1
With Sitecore 7, Sitecore has introduced some cool new features. The most interesting of them all (in my opinion) is the enhanced Search and the ability to write Linq statements for search queries. I have had the opportunity to design a site that uses Linq to Sitecore (Lucene). It definitely speeds up site performance but some search queries on a real site are not as trivial as the ones we have seen in the documentation and demos. In the next couple of posts I will share some of my findings and also try to explain some code fragments. So what is Sitecore's Linq? The Linq layer is an abstract layer that converts common queries to something that a search provider understands. Sitecore currently supports two search providers:
- Lucene
- Solr
Search Configurations Sitecore 7 adds a bunch of new search configuration files. You can find them under /App_Config/Include folder. I'll write a separate post about these files. For the examples in this series of posts I have not modified any of these configuration files. The only thing we need to know at this point is that there are 2 main indexes
- sitecore_master_index: index for the master database
- sitecore_web_index: index for the web database
Basic Concepts
- To perform search we need to specify the index we'll be querying on
- All searches must be performed within a search context.
- Sitecore has a Lucene document mapper that can map the results to a class based on the [IndexField("lucene field name"] attribute for properties. But we'll keep it simple and use one of the SearchTypes (Sitecore.ContentSearch.SearchTypes.SearchResultItem).
- Lucene Document mapper only maps fields that are stored in a Lucene document. It does not fetch field values from the database. (I have seen a lot of people getting confused with this, you can read about Lucene, Document and hits here)
- Multi-list fields values are indexed as normalized (ShortID, lowercase), tokenized GUIDs.
- Lucene field names follow the following rule
- lower case names: if you create a field and call it "Title" its equivalent Lucene field name would be "title"
- spaces are replaced with underscores (_): if you create a template field called "First Name", its equivalent field name would be "first_name"
Simple Searches Find all the items that have "s" in their names To start the search we must get the search index and create a search context
var index = ContentSearchManager.GetIndex("sitecore_master_index");
using (var context = index.CreateSearchContext())
{
Console.WriteLine(
context.GetQueryable().Where(resultItem=>resultItem.Name.Contains("s"))
);
}
Linq converts this query into something that Lucene understands, Lucene query syntax: _name:s On a database with 11061 items this query returns 5134 results in 1.57 seconds. The equivalent fast query for this is
Sitecore.Configuration.Factory.GetDatabase("master").SelectItems("fast://*[@@name='%s%']");
and it takes just under 5 seconds to execute. Things to note
- Linq is faster :-). but it returns a SearchResultItem and not an Item from the database. Which means if we have to access fields that are not stored we'll have to use the Uri or GetItem() method to get the item from the database.
- Fast query is a direct query to the database and returns Sitecore Items
In the example above we are explicitly specifying the index name. This works if we had to query the master database. But what if we wanted to query the context database? Well, that is simple, we can use an overload of the Getindex method, that accepts an item. ContentSearchManager.GetIndex(new SitecoreIndexableItem(Sitecore.Context.Item )) will get the correct index "sitecore_master_index" or "sitecore_web_index" depending on the context database. Using this we can update our original query as:
var index = ContentSearchManager.GetIndex(new SitecoreIndexableItem(Sitecore.Context.Item ));
using (var context = index.CreateSearchContext())
{
Console.WriteLine(
context.GetQueryable().Where(resultItem=>resultItem.Contains("s"))
);
}
A few other examples
// Finding the first item named Sitecore
context.GetQueryable().FirstOrDefault(resultItem=>resultItem.Name=="sitecore")
// Finding the count of items in a database
context.GetQueryable().Count()
// Finding all the items that use a certain template
context.GetQueryable().Where(resultItem=>resultItem.TemplateName=="Sample Item")
// same thing as above but this time using the template id
context.GetQueryable().Where(resultItem=>resultItem.TemplateId==ID.Parse("{76036F5E-CBCE-46D1-AF0A-4143F9B557AA}"))
// all the children of /sitecore/content
context.GetQueryable().Where(resultItem=>resultItem.Paths.Contains(ID.Parse("0de95ae4-41ab-4d01-9eb0-67441b7c2450")))
Slightly Complex Queries So far we have discussed relatively simple queries. Sitecore stores values of all the fields queried above, thus making it easy to write queries. But what if the field value is not stored or is not mapped to SearchResultItem? We can query such fields using the following syntax
// find all items that have some content in the "Page Title" field
context.GetQueryable().Where(resultItem=>resultItem["page_title"]!="")
A few other examples
// find all items that have lorem in the body
context.GetQueryable().Where(resultItem=>resultItem["body"].Contains("lorem"))
// finding an item where item.Fields["Taxonomy Items"] // is a multi list field and contains "{016424DF-865C-49DC-9D7A-40EB8101C717}"
context.GetQueryable()
.Where(resultItem=>resultItem["taxonomy_items"].Contains(Sitecore.ContentSearch.Utilities.IdHelper.NormalizeGuid("{016424DF-865C-49DC-9D7A-40EB8101C717}",true)))
I hope this is helpful. I'll continue with Complex Queries as part of the next post.