Search code examples
elasticsearchasp.net-corenestelasticsearch.net

How can I execute ElasticSearch query on multiple indices with nested mapping


I have two indices with the following configuration with mappings

var settings = new ConnectionSettings(new Uri("http://localhost:9200/"));
settings
      .DefaultMappingFor<ManagementIndex>(m => m
          .IndexName("management")
      )
      .DefaultMappingFor<PropertyIndex>(m => m
          .IndexName("apartmentproperty")
      );
var client = new ElasticClient(settings);


1) Properties mapping

client.Indices.Create("property", i => i
                .Settings(s => s
                    .NumberOfShards(2)
                    .NumberOfReplicas(0)
                )
                .Map<PropertyIndex>(map => map
                    .AutoMap()
                    .Properties(p => p
                        .Nested<PropertyData>(n => n
                            .Name(c => c.property)
                            .AutoMap()
                            .Properties(pp => pp
                                .Text(c => c
                                    .Name(np => np.city)
                                    .Analyzer("standard")
                                )
                                .Text(c => c
                                    .Name(np => np.market)
                                    .Fields(ff => ff
                                        .Text(tt => tt
                                            .Name(np => np.market)
                                            .Analyzer("standard")
                                        )
                                        .Keyword(k => k
                                            .Name("keyword")
                                            .IgnoreAbove(256)
                                        )
                                    )
                                ).Text(c => c
                                    .Name(np => np.name)
                                    .Analyzer("standard")
                                )

                            )
                        )
                    )
                )
            );

and

2) Owner

if (client.Indices.Exists("owner").Exists)
                client.Indices.Delete("owner");

            client.Indices.Create("owner", i => i
                .Settings(s => s
                    .NumberOfShards(2)
                    .NumberOfReplicas(0)
                )
                .Map<OwnerIndex>(map => map
                    .AutoMap()
                    .Properties(p => p
                        .Nested<OwnerProp>(n => n
                            .Name(c => c.owner)
                            .AutoMap()
                            .Properties(pp => pp
                                .Text(c => c
                                    .Name(np => np.market)
                                    .Fields(ff => ff
                                        .Text(tt => tt
                                            .Name(np => np.market)
                                            .Analyzer("standard")
                                        )
                                        .Keyword(k => k
                                            .Name("keyword")
                                            .IgnoreAbove(256)
                                        )
                                    )
                                ).Text(c => c
                                    .Name(np => np.name)
                                    .Analyzer("standard")
                                )
                            )
                        )
                    )
                )
            );

with the following POCO definitions

    public class PropertyData
    {
        public string name { get; set; }
        public string city { get; set; }
        public string market { get; set; }
    }

    public class PropertyIndex
    {
        public PropertyData property { get; set; }
    }

    public class OwnerProp
    {
        public string name { get; set; }
        public string market { get; set; }
    }

    public class OwnerIndex
    {
        public OwnerProp owner { get; set; }
    }


Trying to do a search through the two indices like so

 public async Task<object> SearchPropertiesAsync(string searchQuery, List<string> description, int limit = 25, int skip = 1)
        {
            var propertyfilters = new List<Func<QueryContainerDescriptor<object>, QueryContainer>>();
            var ownerFilters = new List<Func<QueryContainerDescriptor<object>, QueryContainer>>();
            if (description.Any())
            {
                propertyfilters.Add(fq => fq.Terms(t => t.Field("property.market.keyword").Terms(description)));
                ownerFilters.Add(fq => fq.Terms(t => t.Field("owner.market.keyword").Terms(description)));
            }

            var searchResponse = await _elasticClient.SearchAsync<object>(s => s
                .Index(Indices.Index(typeof(PropertyIndex)).And(typeof(OwnerIndex)))

                .Query(q => (q
                    .Nested(n => n

                    .Path(Infer.Field<PropertyIndex>(ff => ff.property))

                    .Query(nq => nq

                    .MultiMatch(m => m

                        .Fields(f => f
                            .Field(Infer.Field<PropertyIndex>(ff => ff.property.city))
                            .Field(Infer.Field<PropertyIndex>(ff => ff.property.market))
                            .Field(Infer.Field<PropertyIndex>(ff => ff.property.name))
                        )
                        .Operator(Operator.Or)
                        .Query(searchQuery)
                        .Fuzziness(Fuzziness.Auto)
                    )  && +q.Bool(bq => bq.Filter(propertyfilters))
                        ))
                    ) || (q
                    .Nested(n => n
                    .Path(Infer.Field<OwnerIndex>(ff => ff.mgmt))
                    .Query(nq => nq

                    .MultiMatch(m => m
                        .Fields(f => f
                            .Field(Infer.Field<OwnerIndex>(ff => ff.owner.market))
                            .Field(Infer.Field<OwnerIndex>(ff => ff.owner.name))
                        )
                        .Operator(Operator.Or)
                        .Query(searchQuery)
                        .Fuzziness(Fuzziness.Auto)
                    ) 
                    && +q.Bool(bq => bq.Filter(ownerFilters))
                    ))
                    )
                ).From((skip - 1) * limit)
                    .Size(limit)
            );

            return searchResponse.Documents;
        }

calling the SearchPropertiesAsync method returns this error messages (truncated for brevity)

         ....
          "index": "owner",
          "caused_by": {
            "type": "illegal_state_exception",
            "reason": "[nested] failed to find nested object under path [property]"
          }

         ....
          "index": "property",
          "caused_by": {
            "type": "illegal_state_exception",
            "reason": "[nested] failed to find nested object under path [owner]"
          }

Notice that it looks like its trying to perform a nested search of owner. on property index and a nested search of property. on owner index which doesnt exist.

I feel like this should be a very trivial problem but I have been using ElasticSearch for only 4days now and still very new into it.

Is there something I am doing wrongly or is there something I am missing. Have searched the whole internet to even arrive at the solution I have at the moment.

Note that when you executed the nested query only one index at a time, the code works fine but trying to execute on multiple Indices is where my problem lies. Any help will be highly appreciated.

I am using ElasticSearch 7.3.2 and Nest Client 7.3.0.

I don't mind downgrading to a lower version that works.


Solution

  • Apparently, according to the docs

    ignore_unmapped (Optional, boolean) Indicates whether to ignore an unmapped path and not return any documents instead of an error. Defaults to false.

    If false, Elasticsearch returns an error if the path is an unmapped field.

    You can use this parameter to query multiple indices that may not contain the field path.

    So chaining .IgnoreUnmapped(true) on the query body for each of the nested query solved the problem. Just in case someone else encounter same problem