How to sort on a string field in Umbraco Examine

Published by Debasish Gracias on 21 August 2024

Examine Sorting Blog Banner

In this article we will learn how you can perform sorting on a string field in Examine. As you know any field that is a numerical or date based is automatically sortable. To make text(string) based fields sortable you need to explicitly opt-in for that behavior.

By default all fields are FieldDefinitionTypes.FullText which are not sortable. To make a text field sortable it needs to be FieldDefinitionTypes.FullTextSortable.

Sorting is done by either the OrderBy or OrderByDescending methods using a SortableField and a SortType. The SortType should typically match the field definition type (i.e. Int, Long, Double, etc...)

  • For FieldDefinitionTypes.FullTextSortable use SortType.String
  • For FieldDefinitionTypes.DateTime use SortType.Long

More information on this can be found here.

Here are the steps below I followed to make my string field sortable in Umbraco Examine. More information can be found here.

STEP 1: Create a ConfigureOptions class

We will start by creating a ConfigureExamineOptions class, that derives from IConfigureNamedOptions<LuceneDirectoryIndexOptions>

using Examine.Lucene;
using Microsoft.Extensions.Options;
namespace Umbraco.Docs.Samples.Web.CustomIndexing;
public class ConfigureExternalIndexOptions : IConfigureNamedOptions<LuceneDirectoryIndexOptions>
{
    public void Configure(string name, LuceneDirectoryIndexOptions options)
    {
        throw new System.NotImplementedException();
    }
    public void Configure(LuceneDirectoryIndexOptions options)
    {
        throw new System.NotImplementedException();
    }
}

In this example we are altering the external index and thus we name the class ConfigureExternalIndexOptions. If you are altering multiple indexes, it is recommended to have separate classes for each index - i.e. ConfigureExternalIndexOptions for the external index, ConfigureInternalIndexOptions for the internal index and so on.

STEP 2: Register this in a composer for it to configure the indexes

using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
namespace Umbraco.Docs.Samples.Web.CustomIndexing;
public class ExamineComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.ConfigureOptions<ConfigureExternalIndexOptions>();
    }
}

STEP 3: Change field value types to FullTextSortable

using Examine;
using Examine.Lucene;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
namespace Umbraco.Docs.Samples.Web.CustomIndexing;
public class ConfigureExternalIndexOptions : IConfigureNamedOptions<LuceneDirectoryIndexOptions>
{
    public void Configure(string name, LuceneDirectoryIndexOptions options)
    {
        if (name.Equals(Constants.UmbracoIndexes.ExternalIndexName))
        {
            options.FieldDefinitions.AddOrUpdate(new FieldDefinition("aggregationTitle", FieldDefinitionTypes.FullTextSortable));
        }
    }
    // Part of the interface, but does not need to be implemented for this.
    public void Configure(LuceneDirectoryIndexOptions options)
    {
        throw new System.NotImplementedException();
    }
}

This will ensure that the ‘aggregationTitle’ field in the index is treated as a FullTextSortable type (if the ‘aggregationTitle’ field does not exist in the index, it is added).

STEP 4: Rebuild Examine index from the Umbraco backoffice

Navigate to the Examine Management section and perform a full rebuild of external index by clicking on the button ‘Rebuild Index’

 

Rebuild Index
Rebuild Index in Examine Management

 

STEP 5: Review Index

Using the same Examine Management section, under the External Index, search for a term using the search box and click on the ‘Search’ button. This will give you the search results and now you will notice that Umbraco has added an special field named __Sort_aggregationTitle.

Review Index
Review Index in Examine Management

 

STEP 6: Use the string field in the Search Service

public class SearchService : ISearchService
    {
        private readonly IExamineManager _examineManager;
        public SearchService(IExamineManager examineManager)
        {
            _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager));
        }
        public ISearchResults? Search(string searchTerm)
        {
            if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out IIndex? index))
            {
                return null;
            }
            IBooleanOperation? query = index.Searcher.CreateQuery(IndexTypes.Content)
                .GroupedNot(new[] { "umbracoNaviHide" }, new[] { "1" });
            string[]? terms = !string.IsNullOrWhiteSpace(searchTerm)
                ? searchTerm.Split(" ", StringSplitOptions.RemoveEmptyEntries)
                .Where(x => !StopAnalyzer.ENGLISH_STOP_WORDS_SET.Contains(x.ToLower()) && x.Length > 2).ToArray() : null;
            if (terms != null && terms.Length > 0)
            {
                query!.And().Group(q => q
                    .GroupedOr(new[] { "aggregationTitle" }, terms), BooleanOperation.Or);
            }
            return query.OrderBy(new SortableField(aggregationTitle, SortType.String)).Execute();
        }
    }

Hurray!! That’s it, this should sort your search results based on a string field.

NOTE:

  1. There is no need to add the sort prefix __Sort_ to the field name when querying. It will still work because that field gets created when it’s set to sortable, and the query builder knows how to use the internal sort field anyway.
  2. While performing tests using this solution, we have noticed that the sort ordering was not working correctly if the word starts with a lower-case letter. So please make sure that word always starts with an upper-case letter.
  3. The same above approach can be followed to sort on a nodeName field.


I would like to say a big thank you to Paul Seal and Callum Whyte for helping me with this solution.

 

Thanks For Reading and Supporting.