Tuesday, November 4, 2014

Sitecore - All about Facet search



Before processing the practical example here I am sharing some term and definition so that we can easily understand what exactly Facet Search is.

What is Faceted Search?
From the user perspective, faceted search breaks up search results into multiple categories, typically showing counts for each, and allows the user to "drill down" or further restrict their search results based on those facets.

Why Faceted Search?
 To control the presentation of search results. In the end, it's about helping the user find the right information. Faceted search gives a user the power to create an individualized navigation path, drilling down through successive refinements to reach the right document. This more effectively mirrors the intuitive thought patterns of most users. Faceted search has become an expected feature, particularly for commerce sites.

How It Works
Faceted search is performed in several parts:
Index: to each document in the index, add tags to specify a value for each facet. For example, for each book in the index, tag it with the type of material and the price range.
Search results: for every search, the solr server returns a count of how many matching documents were tagged with each value within each facet. For example, if the query was for "books," you might find out that in the facet "type of material," your index contains 13 science fiction books, 15 romance novels, and 10 cookbooks; and in the price facet, there are 5 books under $10, 200 books from $10-19.99, and so on.
Query: you can include facet values as query criteria. For example, you can write a query that returns only the romance novels under $10.
Web page: use the facets and document counts returned by the server to create a set of facet links on your web page, like the example shown above. Then construct queries to be activated by each facet link, passing in the appropriate values—say, querying only for romance novels when the user clicks the "Romance (15)" link

Implementing Faceting with Solr
It’s relatively simple to get faceting information from Solr, as there are few prerequisites. Solr offers the following types of faceting, all of which can be requested with no prior configuration:
·         Field faceting – retrieve the counts for all terms, or just the top terms in any given field. The field must be indexed.
·         Query faceting – return the number of documents in the current search results that also match the given query.
·         Date faceting – return the number of documents that fall within certain date ranges



Before processing the practical example here I am sharing some term and definition so that we can easily understand what exactly Facet Search is.


Practical Example:
This example is for person search.
Suppose we have person template in sitecore with some field’s and we already indexed that particular field including the facet field for example,
First name
Last name
Sectors      : this is reference field and we need to facet on this field at the time of searhing.

We have created class named person

public class Person

    {
        [IndexField("firstname_t")]
        public string Firstname { get; set; }
        [IndexField("lastname_t")]
        public string sector { get; set; }
        [IndexField("sector")]
}
  

Then created person search class as below




using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Linq;
using Sitecore.ContentSearch.Utilities;
using Sitecore.Data;

namespace XYZ
{
    public class PeopleSearch
    {
        private static ISearchIndex _index;
        private List<ID> _templateRestrictions = new List<ID>();
        private List<string> _facets = new List<string>();

        private static ISearchIndex Index
        {
            get { return _index ??
            (_index = ContentSearchManager.GetIndex(Sitecore.Configuration.Settings.GetSetting("SearchIndex.Index"))); }
        }

        public List<ID> TemplateRestrictions
        {
            get { return _templateRestrictions; }
            set { _templateRestrictions = value; }
        }

        public List<string> Facets
        {
            get { return _facets; }
            set { _facets = value; }
        }

        public SearchResults<Person> Search(string searchTerm)
        {
             // Create search context - required for searching
            using (var context = Index.CreateSearchContext())
            {
                // Setup a predicate builder as an easy way to build up predicate
                var predicate = PredicateBuilder.True<Person>();
                // Restrict search to limited number of templates (only person items) using an Or on the predicate
                predicate = TemplateRestrictions.Aggregate(predicate, (current, t) => current.Or(p => p.TemplateId == t));
                // Use filter and get an IQueryable
                IQueryable<Person> query = context.GetQueryable<Person>().Filter(predicate);

                // now we can perform filter if we have a search term
                if (!string.IsNullOrEmpty(searchTerm))
                {
                    query = query.Where(i => i.Firstname.Equals(searchTerm).Boost(10) ||
                                        i.Surname.Equals(searchTerm).Boost(20));

                }

                // Apply facets to query
                if (Facets.Any())
                {
                    // Go through and set up facets on the IQueryable
                    query = Facets.Aggregate(query, (current, facetName) => current.FacetOn(c => c[facetName]));
                }

                // Call query and return results
                return query.GetResults();
            }
        }
    }
}


Here we are calling search function:

Sitecore.Data.ID templateId = new Sitecore.Data.ID("{your value");
            PeopleSearch peopleSearch = new PeopleSearch();
            peopleSearch.TemplateRestrictions.Add(templateId);
            // Add a facet
            peopleSearch.Facets.Add("sector");
            // get results
            SearchResults<Person> peopleResults =  peopleSearch.Search("");

            StringBuilder outputBuilder = new StringBuilder();
            outputBuilder.Append("<p>***********Results*************************************</p>");
            outputBuilder.Append("<p>Found: " + peopleResults.TotalSearchResults + " results </p>");

            // Display hits
            foreach (var hit in peopleResults.Hits)
            {
                outputBuilder.Append("<p>Name: " + hit.Document.Firstname + " " + hit.Document.lastname + "</p>");
                if (hit.Document.Role != null)
                {

                    foreach (var sector in hit.Document.sector)
                    {
                        outputBuilder.Append("<p>Role Sitecore Id:" + sector + "</p>");
                    }
                }

                outputBuilder.Append("<p>****************************</p>");
            }

            outputBuilder.Append("<p>***********Facets*************************************</p>");
            // Loop through facet categories
            List<FacetCategory> facetCategories = peopleResults.Facets.Categories;
            foreach (var category in facetCategories)
            {
                outputBuilder.Append("<p>Facet Category Name: " + category.Name + "</p>);
                foreach (var value in category.Values)
                {
                    outputBuilder.Append("<p>Value: " + value.Name + "</p>");
                    outputBuilder.Append("<p>Number of results with this value: " + value.Aggregate + "</p>");
                    outputBuilder.Append("<p>****************************</p>");
                }               
            }

  lblOutput.Text = outputBuilder.ToString();


here is the output

Actual Output

***********Results*************************************

Found: 5 results

Name: test

sector Sitecore Id:501703e57c884ee586793e90650e28cd

sector Sitecore Id:bdf6b678a6fd43ebab6edb22b98958a4

sector Sitecore Id:332e33a9854a44628555272cd4b91ad2

****************************

Name: test

sector Sitecore Id:96352039bf3440d183a8fd92e14a0b72

sector Sitecore Id:332e33a9854a44628555272cd4b91ad2

sector Sitecore Id:963970baae63414da6d44777c103cff9

***********Facets*************************************

Facet Category Name: sector

Value: 332e33a9854a44628555272cd4b91ad2

Number of results with this value: 3

****************************

Value: 501703e57c884ee586793e90650e28cd

Number of results with this value: 3

****************************


I  took some code from fusionworkshop blog.