Sitecore SolR Sorting Challenge
source link: https://blog.coates.dk/2020/03/11/sitecore-solr-sorting-challenge/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
As I promised in my last post (please read it first) here is a solution to address the SolR sorting issues.
The Problem
The issue is that different pages, usually have different date fields to represent how they should be sorted and if we want to adhere to the Helix principles, the Solr feature must NOT KNOW ABOUT PAGE TYPES.
For example, a news page will have a news date, calendar event might use the start date and an some page will not have a date field and therefore will have to use created and or updated.
Typically, I see solutions that deal with this issue at retrieval time i.e. index all the different fields and then have a specific “order by” clause for each page type.
The biggest disadvantages of this approach is that you cannot sort a list with different page types i.e. get the 10 latest items that are either news, event or articles.
In addition, you have to manage all the different order by clauses. Which will destroy the Indexing/SolR abstraction as you will have to expose the IQueryable<T> in order to apply the order by clause.
Solution
I prefer to deal with the sorting issue at indexing time and have a single dedicated SolR field which is used to sort all item types. This allows you to sort news, articles, calendar events, etc. in the same way.
You still must deal with the issue that the SolR implementation should not know about which field to use for a give item type. To overcome this issue we use a configuration file that defines the mapping between an item of a specific type and which field to use for sorting.
Template to Field Mapping
The following configuration defines which field should be stored for sorting for each item template, if a field mapping is not defined, the item updated value is used.
In sitecore, it is easy to map the configuration below to a C# class (i.e. SortFieldMappingRepository) for more information, about how to do this see my blog post on Structured, Type Safe Settings in Sitecore.
<
sitecore
>
<
feature
>
<
SolRIndexing
>
<
SortFieldMappingRepository
type
=
"Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting.SortFieldMappingRepository, Feature.SolRIndexing"
singleInstance
=
"true"
>
<
mappings
hint
=
"raw:Add"
>
<!--News, NewsDate -->
<
sortFieldMapping
templateId
=
"{AE6B4DF2-DF36-4C6D-ABDA-742EE6B85DE9}"
sortFieldIdId
=
"{3D43D709-DFAE-4B4F-8CB2-DF80D9B83857}"
/>
<!-- Calander, StartDate-->
<
sortFieldMapping
templateId
=
"{A8DD1F59-08AB-4BF0-BE76-8873A8F00628}"
sortFieldIdId
=
"{6369AC75-036B-48D8-95E2-F16998F8E777}"
/>
<!-- Video, VideoDate -->
<
sortFieldMapping
templateId
=
"{3D9D8B7A-FCB2-459B-908B-1E31F0C975FB}"
sortFieldIdId
=
"{E9993C21-1EF0-4C30-83D4-5F69923CEC3E}"
/>
<!-- Article, ModifiedDate Field -->
<
sortFieldMapping
templateId
=
"{F6B599F4-11C4-4C65-B253-95F3C40EBA18}"
sortFieldIdId
=
"{DC6C0E49-1705-4F3E-80EF-83176E482DBC}"
/>
</
mappings
>
</
SortFieldMappingRepository
>
</
SolRIndexing
>
</
feature
>
</
sitecore
>
</
configuration
>
Define the SolR index Field
Then we define the SolR index field used for sorting and specify that the SortComputedIndexField class is responsible for adding the sort date to the index.
<
sitecore
>
<
contentSearch
>
<
indexConfigurations
>
<
defaultSolrIndexConfiguration
>
<
documentOptions
>
<
fields
hint
=
"raw:AddComputedIndexField"
>
<!-- Sorting-->
<
field
fieldName
=
"_sort"
returnType
=
"datetime"
>Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting.SortComputedIndexField, Feature.SolRIndexing</
field
>
</
fields
>
</
documentOptions
>
</
defaultSolrIndexConfiguration
>
</
indexConfigurations
>
</
contentSearch
>
</
sitecore
>
The SortComputedIndexField class is responsible for providing the value for the sort field and it calls the CalculateSortDateService to determine the sort value.
namespace
Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting
{
public
class
SortComputedIndexField : AbstractComputedIndexField
{
private
readonly CalculateSortDateService _calculateSortDateService;
public
SortComputedIndexField(CalculateSortDateService calculateSortDateService)
{
_calculateSortDateService = calculateSortDateService;
}
public
SortComputedIndexField()
{
_calculateSortDateService = ServiceLocator.ServiceProvider.GetRequiredService<CalculateSortDateService>();
}
public
override object ComputeFieldValue(IIndexable indexable)
{
Item item = indexable as SitecoreIndexableItem;
if
(item == null)
return
null;
if
(!item.Paths.FullPath.StartsWith(Constants.SitecoreContentRoot))
return
null;
return
_calculateSortDateService.CalculateSortDate(item);
}
}
}
The CalculateSortDateService class iterates over the field mappings, defined in the configuration and uses the field value for the date if the field is found, otherwise the updated value for the item is used.
namespace
Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting
{
public
class
CalculateSortDateService
{
private
readonly SortFieldMappingRepository _sortFieldMappingRepository;
public
CalculateSortDateService([NotNull]SortFieldMappingRepository sortFieldMappingRepository)
{
Assert.ArgumentNotNull(sortFieldMappingRepository, nameof(sortFieldMappingRepository));
_sortFieldMappingRepository = sortFieldMappingRepository;
}
public
DateTime CalculateSortDate([NotNull] Item item)
{
Assert.ArgumentNotNull(item, nameof(item));
var mappings = _sortFieldMappingRepository.Get();
if
(mappings == null)
return
item.Statistics.Updated;
foreach (var sortFieldMapping in mappings.Where(m => m != null))
{
if
(item.TemplateID != sortFieldMapping.TemplateId)
continue
;
Field dateField = item.Fields[sortFieldMapping.SortFieldId];
if
(dateField == null || string.IsNullOrWhiteSpace(item[sortFieldMapping.SortFieldId]))
continue
;
return
new
DateField(dateField).DateTime;
}
return
item.Statistics.Updated;
}
}
}
Sorting Extensions
The last part is to provide the ability to sort the result set and for this we introduce the SortDateSearchResultItem class and a few extensions methods to add sort ascending & descending.
namespace
Feature.SolRIndexing.Infrastructure
{
public
class
SortDateSearchResultItem : SearchResultItem
{
[IndexField(
"_sort"
)]
[DataMember]
public
virtual
DateTime SortDate { get; set; }
}
}
namespace
Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting
{
public
static
class
SortingQueryableExtensions
{
public
static
IQueryable<T> SortDescending<T>(
this
IQueryable<T> query) where T : SortDateSearchResultItem
{
return
query.OrderByDescending(item => item.SortDate);
}
public
static
IQueryable<T> SortAscending<T>(
this
IQueryable<T> query) where T : SortDateSearchResultItem
{
return
query.OrderBy(item => item.SortDate);
}
}
}
I hope this post will help, Alan
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK