(400) Bad Request returned for nested query

Vote:
 

Hello,

I'm trying to make working query with sorting by nested object.

I have the following extension method:

public static IEnumerable<CategorySortOrder> CategoriesSortOrder(this ProductContent product)
{
    if (product == null)
    {
        yield break;
    }

    foreach (NodeEntryRelation relation in _relationRepository.Service.GetParents<NodeEntryRelation>(product.ContentLink))
    {
        yield return new CategorySortOrder
        {
            Category = relation.Parent.ToReferenceWithoutVersion().ToString(),
            SortOrder = relation.SortOrder
        };
    }
}

which I register with custom conventions:

protected override void ApplyProductContentConventions(TypeConventionBuilder<ProductContent> conventionBuilder)
{
    base.ApplyProductContentConventions(conventionBuilder);

    conventionBuilder
        ...
        .IncludeField(x => x.CategoriesSortOrder())
        ...;
}

protected override void ApplyNestedConventions(NestedConventions nestedConventions)
{
    base.ApplyNestedConventions(nestedConventions);

    nestedConventions.ForInstancesOf<ProductContent>()
        .Add(x => x.CategoriesSortOrder());
}

As a result I can see the following data in my Find index:

{
    ...,
    "CategoriesSortOrder$$nested": [
        {
            "SortOrder$$number": 0,
            "___types": [
                "Commerce.Search.Models.CategorySortOrder",
                "System.Object"
            ],
            "Category$$string": "1073741947__CatalogContent",
            "$type": "Commerce.Search.Models.CategorySortOrder, Commerce"
        },
        {
            "SortOrder$$number": 0,
            "___types": [
                "Commerce.Search.Models.CategorySortOrder",
                "System.Object"
            ],
            "Category$$string": "1073741971__CatalogContent",
            "$type": "Commerce.Search.Models.CategorySortOrder, Commerce"
        }
    ],
    ...
}

or in case of no relations:

{
    ...,
    "CategoriesSortOrder$$nested": [],
    ...
}

And what I do during actual search is:

if (!string.IsNullOrWhiteSpace(category))
{
    search = search.OrderBy(p => p.CategoriesSortOrder(), o => o.SortOrder, o => o.Category.MatchCaseInsensitive(category), SortMissing.Last);
}

but unfortunately I receive the following exception:

EPiServer.Find.ServiceException: The remote server returned an error: (400) Bad Request.
SearchPhaseExecutionException[Failed to execute phase [query], all shards failed;
...
Parse Failure [Failed to parse source [...]
...
nested: QueryParsingException[[antonburkovsky_benefitlocal4] [nested] failed to find nested object under path [CategoriesSortOrder]];
...

All other requests without such sorting work fine without any issues.

Thanks

#224263
Edited, Jun 15, 2020 15:45
Quan Mai - Jun 24, 2020 7:57
I don't have an answer for you, but good question - the way you wrote it is easy to understand and it contains all necessary information. Keep it up.
Vote:
 

I have similar code working fine. Could you try using parent id instead of reference as string?

Category = relation.Parent.ID.ToString(),
#224596
Jun 23, 2020 10:19
Anton B - Jun 23, 2020 10:36
I have tried and indexed data in Find has changed but actual search requests still throw the same exception '[nested] failed to find nested object under path [CategoriesSortOrder]'
Vote:
 

So the issue was hidden in the IClient instantiation. Make sure used IClient instance is singleton.

The easiest way to do that is to inject IClient via constructor:

private readonly IClient _client;

public SearchProvider(IClient client)
{
    _client = client;
}
#224683
Jun 24, 2020 10:32
Quan Mai - Jun 24, 2020 10:46
Can you share more information regarding this - sounds like something interesting to look into.
Vote:
 

Great that you solved it. 

#224684
Jun 24, 2020 10:38
Anton B - Jun 24, 2020 10:45
Yeah, I was sadly surpised... I double checked my new functionality million times but missed the very first step previously implemented (and was working fine in common scenarios). Anyway good to have this fixed and thank you for the support.
Vote:
 

Sharing additional details according to the Quan Mai ask.

In the previous implementation we had combo wombo of 2 things.

1) IClient creation (not injection):

private readonly Lazy<IClient> _client;

public SearchProvider()
{
    _client = new Lazy<IClient>(Client.CreateFromConfig);
}

2) SearchProvider transient lifecycle:

public void ConfigureContainer(ServiceConfigurationContext context)
{
    context.Services.AddTransient<ISearchProvider, SearchProvider>();
}

So it causes new IClient instance creation every time when SearchProvider gets called.

And I believe at some moment IClient instance doesn't know about all previously registered custom conventions. And that's why actual search request query is not properly built.

#224689
Edited, Jun 24, 2020 11:10
Quan Mai - Jun 24, 2020 11:24
Interesting. I'm not a Find expert but it looks like Client holds information about the nested conventions. From a framework perspective, it should be forbidden to create new instance of IClient. I will file a bug to Find team so they can look into it. Thanks for sharing.
Anton B - Jun 24, 2020 11:35
Thank you. If it's useful you can refer #492174 Episerver Support ticket because they suggested to check IClient initiation so they are kinda aware of the issue.
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.