A Not So Quick Sitecore Search Primer: Part 2

After a quick foray into Sitecore Search, I decided to jot down a more thorough step-by-step of the things needed to make Sitecore Search from start to finish. All this stuff is probably very well known by Sitecore veterans, but I wanted to get something down for somebody just starting to venture into Sitecore Search. In the Part 1, we setup the index. In this post, we’ll write the code for the search. Please note: I’ve abandoned using Lucene for searches since then and use SOLR now. So the indexing setup will be different, but the search mechanism is more or less the same. This is a big benefit of Sitecore Search.

After the Index

Once the index is setup, the basics of searching using Content Search is pretty simple – you literally call the index and query based on the Content field:

using (var context = ContentSearchManager.GetIndex("indexname").CreateSearchContext())
{
   IQueryable query = context.GetQueryable().Where(p => p.Content.Contains("someterm"))

   foreach(SearchResultItem sri in query)
   {
         ...
   }

}

Advanced Stuff

The above obviously is not enough for anyone to get by with – even the most basic search requirements are more complex than that.

Basic Field Mapping

One of the first things that will be needed is to map your items entities to the index document entity. SearchResultItem is the base object that Sitecore Content Search provides – this maps to a basic Lucene document.

If you want to search by specific fields other than content (for filters, etc), it would be a good idea to extend this class:

public class ResourceSearchResultItem : SearchResultItem

After that, you can add your own fields from your item templates that you indexed using the IndexField attribute:

[IndexField("title")]
public string Title { get; set; }

This works pretty straightforward for string fields – for any multilist values, you have to make sure you defined your property as IEnumerable so that you can get the facets from the search result list.

Computed Fields

If you want a custom value for a field (computed), you will need to create a computed field and then add that to the index, and use that field name for the attribute:

[IndexField("fieldcontent")]
public string FieldString { get; set; }

The computed field class is very simple as well, implemented using IComputedIndexField.

public class FieldContentField : IComputedIndexField
    {
        public string FieldName
        {
            get
            {
                return "FieldContent";
            }
            set
            {
                
            }
        }

        public string ReturnType
        {
            get
            {
                return "String";
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public object ComputeFieldValue(IIndexable indexable)
        {
            Assert.ArgumentNotNull(indexable, "indexable");
            string url = null;
            try
            {
                Item item = indexable as SitecoreIndexableItem;
                
                if (item == null)
                {
                    return null;
                }

                //using the item, you can run business rules and return whatever value you need here
              
            }
            catch (WebException webExc)
            {
                //log error
            }
            return null;
        }
    }

Once done, it needs to get added to the index as a computed field:

<fields hint="raw:AddComputedIndexField">
     <field fieldName="fieldcontent">FieldContentField, AssemblyName</field>
</fields>

Once added, you can now use your own class to do searches:

using (var context = ContentSearchManager.GetIndex("indexname").CreateSearchContext())
{
   IQueryable query = context.GetQueryable().Where(p => p.Title.Contains("someterm"))

   foreach(ResourceSearchResultItem sri in query)
   {
         ...
   }

}

Filters

If you are only searching on one field, the above will work fine. If you want to search on multiple fields, you would need build a predicate.

using (var context = ContentSearchManager.GetIndex("indexname").CreateSearchContext())
{
   //Create an initial predicate - use .True<T> since we'll be AND'ing this clause together 
   var filterPredicate = Sitecore.ContentSearch.Linq.Utilities.PredicateBuilder.True<ResourceSearchResultItem>();
   
   filterPredicate = filterPredicate.And(x => x.ResourceType == "someValue");

}

The above is to chain a string of ‘AND’s. To string together a list of ‘OR’s, you would start with a .False and then string together .Or

If you want to have nested conditions, you’ll need to create a new Predicate, and then add it to the parent:

//create a new predicate
var resourceTypePredicate = PredicateBuilder.False<ResourceSearchResultItem>();
resourceTypePredicate = resourceTypePredicate.Or(x => x.CategoryType == "someValue");

//add it to the parent predicate
filterPredicate = filterPredicate.And(resourceTypePredicate);

And then finally, instead of searching on a specific field, you can now search using the Predicate:

IQueryable<ResourceSearchResultItem> query = context.GetQueryable<ResourceSearchResultItem>().Where(filterPredicate);

Facets

Once you have a search result, you get facets on the results if you’ve made a property for them in your result object. Getting facets is pretty straightforward:

var categoryFacets = new FacetResults();

categoryFacets = query
		 .FacetOn(x => x.CategoryString)
		 .GetFacets();

And then you can get the category values from the Categories and Values properties (see very crude example below):

foreach (var facetCategory in categoryFacets.Categories)
{
    foreach (var facet in facetCategory.Values)
    {
        string theValue = facet.Name;

    }
}

Sorting and Paging

Sorting is very simple – just use the LINQ extension methods to sort on one or multiple fields:

IQueryable<ResourceSearchResultItem> query = context.GetQueryable<ResourceSearchResultItem>()
                                                    .Where(filterPredicate)
                                                    .OrderBy(x => x.ResourceType)
                                                    .ThenBy(x => x.Created)

Paging is also pretty simple, being the results are IEnumerable – you can use the LINQ methods Skip() and Take():

IQueryable<ResourceSearchResultItem> query = context.GetQueryable<ResourceSearchResultItem>()
                                                    .Where(filterPredicate)
                                                    .Skip(0).Take(10); 
                                                    

One issue with this is that you won’t be able to get the total number of results, but Sitecore Search gives you a way to do that:

var numberOfSearchResults = query.TotalHits;

I’m also aware there is a LINQ method called Page(), but I have never tried it.

Conclusion

The above areas will account for 90% of searches. There are other extension methods for the search, such as Like and Matches() – you can use them as you need it, and get the search behavior you are looking for. A lot of this information is already available, but I was having trouble finding it all in one place. Hopefully, this is informative and convenient for the someone who is looking for a soup-to-nuts guide on searches.

Advertisement

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 )

Connecting to %s