Posted on 19/03/2026 22:33:54
The query is a query - passed to Lucene and Lucene will always sort - if nothing specified, it will use Score - which is probably the same numeric value, but a sorting algorithm will do what it does.
To keep the order of the search result it can be done when rendering the result, you need to do that in the template.
Or maybe a notification subscriber - AI says this:
using System;
using System.Collections.Generic;
using System.Linq;
using Dynamicweb;
using Dynamicweb.Core;
using Dynamicweb.Extensibility.Notifications;
using Dynamicweb.Indexing;
using QueryNotifications = Dynamicweb.Indexing.Notifications.Query;
namespace Dynamicweb.Ecommerce.Products.NotificationSubscribers;
/// <summary>
/// Re-sorts query results to match the order of the MainProductId query string parameter.
/// </summary>
[Subscribe(QueryNotifications.AfterQuery)]
public sealed class MainProductIdQuerySortSubscriber : NotificationSubscriber
{
/// <summary>
/// Runs late so other subscribers can finish first.
/// </summary>
public override int Rank => 1000;
/// <summary>
/// Handles the AfterQuery notification.
/// </summary>
/// <param name="notification">The notification name.</param>
/// <param name="args">The notification arguments.</param>
public override void OnNotify(string notification, NotificationArgs args)
{
if (!string.Equals(notification, QueryNotifications.AfterQuery, StringComparison.OrdinalIgnoreCase))
return;
if (args is not QueryNotifications.AfterQueryArgs afterQueryArgs || afterQueryArgs.Result is null)
return;
string? mainProductIdsValue = Context.Current?.Request?["MainProductId"];
if (string.IsNullOrWhiteSpace(mainProductIdsValue))
return;
var requestedIds = mainProductIdsValue
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (requestedIds.Length == 0)
return;
var documents = afterQueryArgs.Result.QueryResult.ToList();
if (documents.Count == 0 || documents.Count != requestedIds.Length)
return;
var positions = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < requestedIds.Length; i++)
{
if (!positions.TryAdd(requestedIds[i], i))
return;
}
var sortedDocuments = new object[documents.Count];
foreach (var document in documents)
{
if (document is not IndexDocument indexDocument)
return;
if (!TryGetDocumentMainProductId(indexDocument, out string documentMainProductId))
return;
if (!positions.TryGetValue(documentMainProductId, out int position))
return;
sortedDocuments[position] = indexDocument;
}
if (sortedDocuments.Any(document => document is null))
return;
afterQueryArgs.Result.QueryResult = sortedDocuments;
afterQueryArgs.Result.Count = sortedDocuments.Length;
}
private static bool TryGetDocumentMainProductId(IndexDocument document, out string mainProductId)
{
if (TryGetStringValue(document, "ProductNumber", out mainProductId))
return true;
if (TryGetStringValue(document, "ID", out mainProductId))
return true;
mainProductId = string.Empty;
return false;
}
private static bool TryGetStringValue(IndexDocument document, string fieldName, out string value)
{
if (document.TryGetValue(fieldName, out object? rawValue))
{
value = Converter.ToString(rawValue);
if (!string.IsNullOrWhiteSpace(value))
return true;
}
value = string.Empty;
return false;
}
}