Using Solr to group search results

Current version: 9.2
Note

This topic is valid for Sitecore 9.0 and later.

Solr has a result grouping feature. You use this feature to sort documents into groups, based on a common field value. Solr returns the top documents for each group.

For example, if a user searches for "DVD", Solr can return three categories such as "TV and Video," "Movies," and "Computers," with three results per category. In this case, the query term "DVD" appears in all three categories. Solr groups the results together in order to be more useful for the end-users.

You do not have to configure anything in Solr to use this feature.

There is more information here:

https://cwiki.apache.org/confluence/display/solr/Result+Grouping

Use with Sitecore

Sitecore has an API that you use to access the result grouping feature of Solr. The API is designed as an extension to the existing ContentSearch API.

Follow these steps to use the API in your project:

  1. Reference the following DLL files into your project (you can get them from the bin folder of your Sitecore website):

    • SolrNet.dll

    • Sitecore.ContentSearch.dll

    • Sitecore.ContentSearch.SolrProvider.dll

    • Sitecore.ContentSearch.SolrNetExtension.dll

  2. Import the following namespace:

    • Sitecore.ContentSearch.SolrNetExtension (an extension of SolrNet that contains the implementation of the SuggesterComponent)

You can now use the Result Grouping API as part of ISearchIndex:

  1. Create your query as string or any implementation of ISolrQuery.

  2. Call IProviderSearchContext.Query() method and inspect the returned result of type SolrQueryResults<T>.

The returned result contains a Grouping property that is a Dictionary<string, GroupedResults<T>>, where Key is the group field name and Value is an object of GroupedResults that represents the count of unique matching documents that are grouped along with the list of grouped documents (ICollection<Group<T>>).

Each Group<T> object contains the Solr documents, group value, and the number of documents in the group.

For example:

API documentation

Query

Executes the query against Solr.

Syntax

RequestResponse
public static SolrQueryResults<T> Query<T>(
 this IProviderSearchContext context, 
 string q, 
 QueryOptions options
)

Parameters

  • Context

    Type: Sitecore.ContentSearch.IProviderSearchContext

    The search provider context

  • q

    The Solr query string

  • Options

    Type: SolrNet.Commands.Parameters.QueryOptions

    Query options

Type parameters

  • T

    The type of the result item

Remarks

The method will execute the query (the first argument) against Solr with the defined set of options (the second parameter) and retrieve the result as a dictionary of values.

Example

RequestResponse
The following example illustrates the basic usage of result grouping.
<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import namespace="Sitecore.ContentSearch.SolrProvider.SolrNetIntegration" %>
<%@ Import namespace="Sitecore.ContentSearch" %>
<%@ Import namespace="Sitecore.Configuration" %>
<%@ Import namespace="Sitecore.Data" %>
<%@ Import namespace="Sitecore.Data.Items" %>
<%@ Import namespace="SolrNet.Commands.Parameters" %>
<%@ Import namespace="Sitecore.ContentSearch.SearchTypes" %>
<%@ Import Namespace="Sitecore.SecurityModel" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Solr | Grouping results demo</title>
    <script runat="server">
        private void PerformSearch(object sender, EventArgs e)
        {
            using (var ctx = ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext())
            {
                var input = string.IsNullOrEmpty(searchTextBox.Value) ? "*" : searchTextBox.Value.Replace(" ", "+");
                string q = String.Format("(name_t:{0} OR color_t:{0}) AND _parent:110d559fdea542ea9c1c8a5df7e70ef9", input);
                var options = new QueryOptions
                                  {
                                      Grouping = new GroupingParameters
                                                     {
                                                         Fields = new [] { "type_t" },
                                                         Limit = 10
                                                     }
                                  };
                var result = ctx.Query<SitecoreUISearchResultItem>(q, options);
                StringBuilder htmlBuilder = new StringBuilder();
                foreach (var group in result.Grouping)
                {
                    foreach (var grp in group.Value.Groups)
                    {
                        htmlBuilder.Append("<div class=\"group\">");
                        htmlBuilder.AppendFormat("<div class=\"group-header\">{0}</div>", grp.GroupValue);
                        htmlBuilder.Append("<div class=\"group-item\">");
                        foreach (var item in grp.Documents)
                        {
                            htmlBuilder.AppendFormat("<span>{0} ({1})</span>", item["Name"], item["Color"]);
                        }
                        htmlBuilder.Append("</div>");
                        htmlBuilder.Append("</div>");
                    }
                }
                searchResults.InnerHtml = htmlBuilder.ToString();
            }
        }
        private void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack && Request["create"] == "true")
            {
                using (new SecurityDisabler())
                {
                    GenerateTheItems();
                }
            }
        }
        #region Create the demo items
        private void GenerateTheItems()
        {
            var master = Factory.GetDatabase("master");
            if (master.GetItem("/sitecore/templates/sample/Vehicle") != null)
            {
                return;
            }
            var sampleTemplates = master.GetItem("/sitecore/templates/sample");
            var vehicleTemplate = CreateTemplate(master, sampleTemplates);
            var home = master.GetItem("/sitecore/content/Home");
            CreateItem(home, vehicleTemplate, "Toyota", "Corolla", "Red", "Sedan");
            CreateItem(home, vehicleTemplate, "Toyota", "Avanza", "Blue", "MPV");
            CreateItem(home, vehicleTemplate, "Toyota", "Camry", "Blue", "Sedan");
            CreateItem(home, vehicleTemplate, "Honda", "Civic", "White", "Sedan");
            CreateItem(home, vehicleTemplate, "Perodua", "Myvi", "White", "Hatchback");
            CreateItem(home, vehicleTemplate, "Perodua", "Myvi", "Red", "Hatchback");
            Response.Write("Done!");
        }
        private void CreateItem(Item home, TemplateItem vehicleTemplate, string model, string name, string color, string type)
        {
            var car = home.Add(model, vehicleTemplate);
            car.Editing.BeginEdit();
            car["Color"] = color;
            car["Type"] = type;
            car["Name"] = name;
            car.Editing.EndEdit();
        }
        private TemplateItem CreateTemplate(Database master, Item parent)
        {
            var vehicleTemplate = master.Templates.CreateTemplate("Vehicle", parent);
            var dataSection = vehicleTemplate.AddSection("Data");
            dataSection.AddField("Type");
            dataSection.AddField("Color");
            dataSection.AddField("Name");
            return vehicleTemplate;
        } 
        #endregion
    </script>
    <style>
        .group {
            font-family: sans-serif;
            border: #aecce6 solid 1px;
            margin: 10px;
        }
        .group-header {
            font-size: larger;
            font-weight: bold;
            background-color: aliceblue;
            padding: 10px;
        }
        .group-item {
            padding: 10px;
            display: grid;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <div align="center">
            <input type="text" id="searchTextBox" style="width: 80%;" placeholder="Enter car model or color" runat="server" autocomplete="off" />
            <asp:Button Text="Search" OnClick="PerformSearch" runat="server" />
        </div>
        <div id="searchResults" runat="server"></div>
    </form>
</body>
</html>

Do you have some feedback for us?

If you have suggestions for improving this article,