Search code examples
c#elasticsearch

Sort by multiple fields in ElasticSearch C#


I want to sort my response by rank_tx Desc followed by _score Desc. Currently, I can do by rank_tx or _score only.

.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
    .........
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.9.1" /
    ........

C# code

var response = await _client.SearchAsync<Model>(search =>
{
    search.Index(_indexOptions.Index)
         .Query(q => q
            .Bool(b => b
                .Should(
                    sh => sh.MatchPhrasePrefix(m => m.Field(f => f.field1).Query(input)),
                    sh => sh.MatchPhrase(m => m.Field(f => f.field1).Query(input).Slop(50))
                )
            )
         )
        .Size(50)
        .Sort(s => s
             .Field(Infer.Field<Model>(f => f.rank_tx), fs => fs.Order(SortOrder.Desc)))
        .Highlight(highlightOptions);
}, cancellationToken);

This work's fine on Console.

GET /my_index/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_phrase_prefix": {
            "my_field_1": {
              "query": input
            }
          }
        },
        {
          "match_phrase": {
            "my_field_2": {
              "query": input,
              "slop": 50
            }
          }
        }
      ]
    }
  },
  "sort": [
    { "rank_tx": { "order": "desc" } },
    { "_score": { "order": "desc" } }
  ],
  "size": 50
}

I have gone through official documentation, couldn't get the solution.

These fixes didn't work:-

  1. Added two sort fields in sort
 .Sort(s => s
      .Field(Infer.Field<Model>(f => f.rank_tx), fs => fs.Order(SortOrder.Desc))
      .Field("_score", fs => fs.Order(SortOrder.Desc)))
  1. Used SortOptionsDescriptor
var sortFields = new SortOptionsDescriptor<Model>()
            .Field(Infer.Field<Model>(f => f.rank_tx), fs => fs.Order(SortOrder.Desc))
            .Field("_score", fs => fs.Order(SortOrder.Desc));

.Sort(sortFields)

On both, I got response sorted by _score, which means It considers last field only.


Solution

  • Issue

    In both fixes, I was reconfiguring the same SortOptionsDescriptor, that's why only the final call applies to the final object.

    .Sort(s => s
          .Field(Infer.Field<Model>(f => f.rank_tx), fs => fs.Order(SortOrder.Desc))
          .Field("_score", fs => fs.Order(SortOrder.Desc)))
    
    var sortFields = new SortOptionsDescriptor<Model>()
                .Field(Infer.Field<Model>(f => f.rank_tx), fs => fs.Order(SortOrder.Desc))
                .Field("_score", fs => fs.Order(SortOrder.Desc));
    
    .Sort(sortFields)
    

    Solution

    There exists an overload of sort accepting a params Action<SortOptionsDescriptor<TDocument>>[]

    var response = await _client.SearchAsync<Model>(search =>
    {
        search.Index(_indexOptions.Index)
             .Query(q => q
                .Bool(b => b
                    .Should(
                        sh => sh.MatchPhrasePrefix(m => m.Field(f => f.field1).Query(input)),
                        sh => sh.MatchPhrase(m => m.Field(f => f.field1).Query(input).Slop(50))
                    )
                )
             )
            .Size(50)
            .Sort(
                s => s.Field(Infer.Field<Model>(f => f.rank_tx), fs => fs.Order(SortOrder.Desc)),
                s => s.Field("_score", fs => fs.Order(SortOrder.Desc)))
            .Highlight(highlightOptions);
    }, cancellationToken);