Sitecore ContentSearch – Get items from SOLR or Lucene – A base class implementation

Reading items from Sitecore is pretty straight forward:

Sitecore.Data.Items.Item item = 
   Sitecore.Context.Database.GetItem("/sitecore/context/.../...");

And it is fast, unless you need to retrieve items from many paths, or need to retrieve every child of a certain base class. In these situations you resolve to using the built in ContentSearch, which is a Lucene or SOLR index.

When working with objects from the ContentSearch API you will have to create your own model classes that maps the indexed fields to class properties. This is done using an IndexFieldAttribute to the properties of the class that will represent the indexed data:

[IndexField("customerpage")]
public ID CustomerPageId {	get; internal set; }

[IndexField("customername")]
public string CustomerName { get; internal set; }

The default indexes called sitecore_core_index, sitecore_master_index and sitecore_web_index is born with a long list of default properties that is useful for every class. Because of this it makes sense to let every one of your model classes inherit from a base class that maps these fields for you.

So let’s code.

STEP 1: CREATE A BASE CLASS

This base class maps the most common fields. There are many more for you to explore, but this particular class have been the base class of a huge project that I have been working on for the past 4 years:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Sitecore.Configuration;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Converters;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace MySearch
{
  [Serializable]
  public abstract class SearchResultItem
  {
    [NonSerialized]
    private Item _item;

    // Get the actual Sitecore item. Beware that using this property 
    // will substantially slow your query, as it looks up the item
    // in Sitecore. Use with caution, and try to avoid using it in
    // LINQ or enumerations 
    public virtual Item Item
    {
      get { return _item ?? (_item = GetItem()); } set { _item = value; }
    }

    // Returns the Item ID (in SOLR this is stored as a short GUID in the _group field)
    [IndexField(Sitecore.ContentSearch.BuiltinFields.Group)]
    [TypeConverter(typeof(IndexFieldIDValueConverter))]
    public virtual ID ItemId
    {
      get; set;
    }

    // This is a combined key describing the Sitecore item in details
    // For example: sitecore://web/{7102ee6b-6361-41ad-a47f-832002082a1a}?lang=da&ver=1&ndx=sitecore_web_index
    // With the ItemUri class you can extract the individual values like database, id, language, version
    [IndexField(Sitecore.ContentSearch.BuiltinFields.UniqueId)]
    [TypeConverter(typeof(IndexFieldItemUriValueConverter))]
    public virtual ItemUri ItemUri
    {
      get; set;
    }

    // Return the item language
    [IndexField(Sitecore.ContentSearch.BuiltinFields.Language)]
    public virtual string Language
    {
      get; set;
    }

    // Returns true if the item is the latest version. When reading from the
    // web database index, this will alwaus be true.    
    [IndexField(Sitecore.ContentSearch.BuiltinFields.LatestVersion)]
    public bool IsLatestVersion
    {
      get; set;
    }

    // Returns the ID's of every parent sorted by top parent first
    [IndexField(Sitecore.ContentSearch.BuiltinFields.Path)]
    [TypeConverter(typeof(IndexFieldEnumerableConverter))]
    public virtual IEnumerable<ID> ItemAncestorsAndSelf
    {
      get; set;
    }

    // Returns the updated datetime
    [IndexField(Sitecore.ContentSearch.BuiltinFields.SmallUpdatedDate)]
    public DateTime Updated
    {
      get; set;
    }

    // Returns every template that this item implements and inherits
    [IndexField(Sitecore.ContentSearch.BuiltinFields.AllTemplates)]
    [TypeConverter(typeof(IndexFieldEnumerableConverter))]
    public virtual IEnumerable<ID> ItemBaseTemplates
    {
      get; set;
    }

    private Item GetItem()
    {
      Assert.IsNotNull(ItemUri, "ItemUri is null.");
      return Factory.GetDatabase(ItemUri.DatabaseName).GetItem(ItemUri.ItemID, ItemUri.Language, ItemUri.Version);
    }
  }
}

STEP 2: CREATE A MODEL CLASS FOR A SPECIFIC TEMPLATE

This example inherits from the SearchResultItem base class, and encapsulates a Customer template containing 2 fields, CustomerPage and CustomerName.

namespace MySearch
{
  [DataContract]
  [Serializable]
  public class CustomerModel : SearchResultItem
  {
    [DataMember]
    [IndexField("customername")]
    public string CustomerName { get; internal set; }

    [IndexField("customerpage")]
    public ID CustomerPageId { get; internal set; }
  }
}

STEP 3: USING THE BASE CLASS TO SEARCH USING PREDICATES

A Predicate are a Latin word for “making search soo much easier”. Predicates defines reusable static functions. When run, Predicates become part of the index query itself, further improving performance. So let’s start by making 3 predicates:

namespace MySearch
{
  public static class Predicates
  {
    // Ensure that we only return the latest version
    public static Expression<Func<T, bool>> IsLatestVersion<T>() where T : SearchResultItem
    {
      return searchResultItem => searchResultItem.IsLatestVersion;
    }

    // Ensure that the item returned is based on, or inherits from the specified template
    public static Expression<Func<T, bool>> IsDerived<T>(ID templateID) where T : SearchResultItem
    {
      return searchResultItem => searchResultItem.ItemBaseTemplates.Contains(templateID);
    }

    // Ensure that the item returned is a content item by checking that the 
    // content root is part of the item path 
    public static Expression<Func<T, bool>> IsContentItem<T>() where T : SearchResultItem
    {
      return searchResultItem => searchResultItem.ItemAncestorsAndSelf.Contains(ItemIDs.ContentRoot);
    }
  }
}

With these predicates in place, I can create a repository for my Customer items:

namespace MySearch
{
  public class CustomerModelRepository
  {
    private readonly Database _database;

    public CustomerModelRepository() : this(Context.Database)
    {
    }

    public CustomerModelRepository(Database database)
    {
      _database = database;
    }

    public IEnumerable<CustomerModel> GetAll()
    {
      return Get(PredicateBuilder.True<CustomerModel>());
    }

    private IEnumerable<CustomerModel> Get(Expression<Func<CustomerModel, bool>> predicate)
    {
      using (IProviderSearchContext context = GetIndex(_database).CreateSearchContext(SearchSecurityOptions.DisableSecurityCheck))
      {
        return context.GetQueryable<CustomerModel>()
          .Where(Predicates.IsDerived<CustomerModel>(new ID("{1EB6DC02-4EBD-427A-8E36-7D2327219B6C}")))
          .Where(Predicates.IsLatestVersion<CustomerModel>())
          .Where(Predicates.IsContentItem<CustomerModel>())
          .Where(predicate).ToList();
      }
    }
    
    private static ISearchIndex GetIndex(Database database)
    {
      Assert.ArgumentNotNull(database, "database");
      switch (database.Name.ToLowerInvariant())
      {
        case "core":
          return ContentSearchManager.GetIndex("sitecore_core_index");
        case "master":
          return ContentSearchManager.GetIndex("sitecore_master_index");
        case "web":
          return ContentSearchManager.GetIndex("sitecore_web_index");
        default:
          throw new ArgumentException(string.Format("Database '{0}' doesn't have a default index.", database.Name));
      }
    }
  }
}

The private Get() method returns every index item following these criteria:

  • Must implement or derive from the template with the specified GUID (the GUID of the Customer template) = Predicates.IsDerived
  • And must be the latest version = Predicates.IsLatestVersion
  • And must be a content item = Predicates.IsContentItem

The repository is used like this:

CustomerModelRepository rep = new CustomerModelRepository(Sitecore.Context.Database);
IEnumerable<CustomerModel> allCustomers = rep.GetAll();
foreach (CustomerModel customer in allCustomers)
{
  // do something with the customer
  customer.CustomerName;
}

I hope this introduction will help you create your own base class implementation and start making fast content searches.

MORE TO READ:

For more SOLR knowledge, you should read my colleague Søren Engel‘s posts about SOLR:

These resources could also be helpful:

 

Advertisements

About briancaos

Developer at Pentia A/S since 2003. Have developed Web Applications using Sitecore Since Sitecore 4.1.
This entry was posted in .net, c#, General .NET, Sitecore 7, Sitecore 8 and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s