Search code examples
c#elasticsearchnest

NEST - MultiMatch search all documents when term is empty - Elasticsearch 6.4


I am trying to query using NEST and the multimatch option, but the results are not coming out as expected.

I submit a term that should be compared to several fields. However, if you do not set a search term, all documents must be returned.

I saw that it was possible to use a keyword like "*. *" But it did not work. Any suggestion?

var searchResponse = client.Search<DocumentElasticModel>(s => s
              .Size(pageSize)
              .Skip(currentPageIndex * pageSize)
              .Sort(ss => ss
                .Descending(SortSpecialField.Score)
              )
              .Source(sf => sf
                .Includes(i => i
                    .Fields(
                        returnedFields
                    )
                )
              )
              .Query(q => q
                .Nested(c => c
                    .Name("named_query")
                    .Boost(1.1)
                    .InnerHits(i => i.Explain())
                    .Path(p => p.PerguntasRespostas)
                    .Query(nq => nq
                        .MultiMatch(m => m
                            .Fields(f => filterFields) 
-----------------------WHEN THE 'SEARCH' IS EMPTY, SHOULD FIND ALL -----------------
                            .Query(string.IsNullOrEmpty(search) ? string.Empty : search)
                        )
                    )
                    .IgnoreUnmapped()
                )
              )

Solution

  • NEST supports this by default with a concept referred to as Conditionless queries. You can

    private static void Main()
    {
        var defaultIndex = "documents";
        var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
    
        var settings = new ConnectionSettings(pool, new InMemoryConnection())
            .DefaultIndex(defaultIndex)
            .DisableDirectStreaming()
            .PrettyJson()
            .OnRequestCompleted(callDetails =>
            {
                if (callDetails.RequestBodyInBytes != null)
                {
                    Console.WriteLine(
                        $"{callDetails.HttpMethod} {callDetails.Uri} \n" +
                        $"{Encoding.UTF8.GetString(callDetails.RequestBodyInBytes)}");
                }
                else
                {
                    Console.WriteLine($"{callDetails.HttpMethod} {callDetails.Uri}");
                }
    
                Console.WriteLine();
    
                if (callDetails.ResponseBodyInBytes != null)
                {
                    Console.WriteLine($"Status: {callDetails.HttpStatusCode}\n" +
                             $"{Encoding.UTF8.GetString(callDetails.ResponseBodyInBytes)}\n" +
                             $"{new string('-', 30)}\n");
                }
                else
                {
                    Console.WriteLine($"Status: {callDetails.HttpStatusCode}\n" +
                             $"{new string('-', 30)}\n");
                }
            });
    
        var client = new ElasticClient(settings);
    
        var pageSize = 20;
        var currentPageIndex = 0;  
        string search = "foo";
    
    
        var searchResponse = client.Search<DocumentElasticModel>(s => s
            .Size(pageSize)
            .Skip(currentPageIndex * pageSize)
            .Sort(ss => ss
                .Descending(SortSpecialField.Score)
            )
            .Source(sf => sf
                .Includes(i => i
                    .Fields(f => f.TopLevelMessage)
                )
            )
            .Query(q => q
                .Nested(c => c
                    .Name("named_query")
                    .Boost(1.1)
                    .InnerHits(i => i.Explain())
                    .Path(p => p.PerguntasRespostas)
                    .Query(nq => nq
                        .MultiMatch(m => m
                            .Fields(f => f
                                .Field(ff => ff.PerguntasRespostas.First().Message)
                            ) 
                            .Query(search)
                        )
                    )
                    .IgnoreUnmapped()
                )
            )
        );
    }
    
    
    public class DocumentElasticModel 
    {
        public string TopLevelMessage { get; set; }
    
        public IEnumerable<PerguntasRespostas> PerguntasRespostas {get;set;}
    }
    
    public class PerguntasRespostas
    {
        public string Message { get; set; }  
    }
    

    This would send the following query

    POST http://localhost:9200/documents/documentelasticmodel/_search
    {
      "from": 0,
      "size": 20,
      "sort": [
        {
          "_score": {
            "order": "desc"
          }
        }
      ],
      "_source": {
        "includes": [
          "topLevelMessage"
        ]
      },
      "query": {
        "nested": {
          "_name": "named_query",
          "boost": 1.1,
          "query": {
            "multi_match": {
              "query": "foo",
              "fields": [
                "perguntasRespostas.message"
              ]
            }
          },
          "path": "perguntasRespostas",
          "inner_hits": {
            "explain": true
          },
          "ignore_unmapped": true
        }
      }
    }
    

    Now, if you change search to string.Empty or null, you get

    POST http://localhost:9200/documents/documentelasticmodel/_search
    {
      "from": 0,
      "size": 20,
      "sort": [
        {
          "_score": {
            "order": "desc"
          }
        }
      ],
      "_source": {
        "includes": [
          "topLevelMessage"
        ]
      }
    }
    

    With no explicit "query" in the request, this is the same as a match_all query.

    If you wanted to override the conditionless query feature in NEST, you can mark the query as .Verbatim() and NEST will send exactly as is

    var searchResponse = client.Search<DocumentElasticModel>(s => s
        .Size(pageSize)
        .Skip(currentPageIndex * pageSize)
        .Sort(ss => ss
            .Descending(SortSpecialField.Score)
        )
        .Source(sf => sf
            .Includes(i => i
                .Fields(f => f.TopLevelMessage)
            )
        )
        .Query(q => q
            .Nested(c => c
                .Verbatim() // <-- mark the nested query
                .Name("named_query")          
                .Boost(1.1)
                .InnerHits(i => i.Explain())
                .Path(p => p.PerguntasRespostas)
                .Query(nq => nq
                    .MultiMatch(m => m
                        .Verbatim() // <-- mark the inner query
                        .Fields(f => f
                            .Field(ff => ff.PerguntasRespostas.First().Message)
                        ) 
                        .Query(search)
                    )
                )
                .IgnoreUnmapped()
            )
        )
    );
    

    which sends

    POST http://localhost:9200/documents/documentelasticmodel/_search
    {
      "from": 0,
      "size": 20,
      "sort": [
        {
          "_score": {
            "order": "desc"
          }
        }
      ],
      "_source": {
        "includes": [
          "topLevelMessage"
        ]
      },
      "query": {
        "nested": {
          "_name": "named_query",
          "boost": 1.1,
          "query": {
            "multi_match": {
              "fields": [
                "perguntasRespostas.message"
              ]
            }
          },
          "path": "perguntasRespostas",
          "inner_hits": {
            "explain": true
          },
          "ignore_unmapped": true
        }
      }
    }
    

    You'll need to check if this is a valid query that Elasticsearch accepts.