Search code examples
c#elasticsearchnest

How to search dynamic elements of array in ElasticSearch using C# NEST?


I have these data in my elasticsearch with this structure.

enter image description here

How do I search firstName, middleName and surname from this array? Note: The NameDetails array length is dynamic. Person A could have just one element of NameDetails. Person B could have 3 elements of NameDetails.

At the moment, I could only search by gender. I am using NEST nuget C#. This is my query.

var response = await _elasticClient.SearchAsync<Person>(s => s
          .Query(q => q
              .Bool(b => b.Must(
                mu => mu
                .Match(m => m
                 .Field(f => f.Gender)
                 .Query(Gender)
                )

                )
              )
          )
        );

enter image description here

In NEST, I tried with this code but return no result.

        var response = _elasticClient.Search <Model.Entities.Split.Person.Person> (s => s
            .Index("person")
            .Query(q => q
                .Match(m => m
                    .Field(f => f.NameDetails.Name[0].NameValue.FirstName)
                    .Query("Fawsu")
                )
            )
        );

But if I directly run the DSL query at ElasticSearch with query below, it returns result.

GET /person/_search
{
  "query": {
    "match": {
          "nameDetails.nameValue.firstName": {
            "query": "Fawsu"
          }
        }
    }
  }
}

or

GET /person/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "fuzzy": {
            "nameDetails.nameValue.surname": {
              "value": "Pibba",
              "fuzziness": "AUTO"
            }
          }
        },
        
        {
          "fuzzy": {
            "nameDetails.nameValue.firstName": {
              "value": "Fawsu",
              "fuzziness": "AUTO"
            }
          }
        }
      ]
    }
  }
}

Solution

  • It's useful to see how this is mapped to a POCO

    public class Person
    {
        public string Gender { get; set; }
        public string ActiveStatus { get; set; }
        public string Deceased { get; set; }
        public List<Name> NameDetails { get; set; }
    }
    
    public class Name
    {
        public List<NameValue> NameValue { get; set; }
        public string NameType { get; set; }
    }
    
    public class NameValue
    {
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
    }
    

    (You might map some of these fields as types other than string, I'll leave that as an exercise for the reader).

    To search on first name

    var client = new ElasticClient();
    
    
    var response = client.Search<Person>(s => s
        .Index("people")
        .Query(q => q
            .Match(m => m
                .Field(f => f.NameDetails[0].NameValue[0].FirstName)
                .Query("Fawsu")
            )
        )
    );
    

    which yields the query

    POST http://localhost:9200/people/_search?pretty=true&typed_keys=true 
    {
      "query": {
        "match": {
          "nameDetails.nameValue.firstName": {
            "query": "Fawsu"
          }
        }
      }
    }
    

    The expression in .Field(f => f...) is an expression to build the path to "nameDetails.nameValue.firstName", which will look for a match in any first name of name value and name details. The fact it uses indexers does not mean that it is targeting the first name value of the first name details, but are simply a way to traverse the object graph to build the expression.

    To build a composite query to target multiple values of the same name value, both Name and NameValue would need to be mapped as nested data types, and then nested queries would be used.

    For a single field

    var response = client.Search<Person>(s => s
        .Index("people")
        .Query(q => q
            .Nested(n => n
                .Path(f => f.NameDetails)
                .Query(nq => nq
                    .Nested(nn => nn
                        .Path(f => f.NameDetails[0].NameValue)
                        .Query(nnq => nnq
                            .Match(m => m
                                .Field(f => f.NameDetails[0].NameValue[0].FirstName)
                                .Query("Fawsu")
                            )
                        )
                    )
                )
            )
        )
    );
    

    For multiple fields

    var response = client.Search<Person>(s => s
        .Index("people")
        .Query(q => q
            .Nested(n => n
                .Path(f => f.NameDetails)
                .Query(nq => nq
                    .Nested(nn => nn
                        .Path(f => f.NameDetails[0].NameValue)
                        .Query(nnq => nnq
                            .Bool(b => b
                                .Must(m => m
                                    .Match(m => m
                                        .Field(f => f.NameDetails[0].NameValue[0].FirstName)
                                        .Query("Fawsu")
                                    ), m => m
                                    .Match(m => m
                                        .Field(f => f.NameDetails[0].NameValue[0].LastName)
                                        .Query("Pibba")
                                    )
                                )
                            )
                        )
                    )
                )
            )
        )
    );
    

    the latter of which results in the query

    POST http://localhost:9200/people/_search?pretty=true&typed_keys=true 
    {
      "query": {
        "nested": {
          "path": "nameDetails",
          "query": {
            "nested": {
              "path": "nameDetails.nameValue",
              "query": {
                "bool": {
                  "must": [
                    {
                      "match": {
                        "nameDetails.nameValue.firstName": {
                          "query": "Fawsu"
                        }
                      }
                    },
                    {
                      "match": {
                        "nameDetails.nameValue.lastName": {
                          "query": "Pibba"
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }