How to search in ElasticSearch nested objects using NEST

Aditi Ananya picture Aditi Ananya · Jun 23, 2015 · Viewed 10.6k times · Source

I'm trying to search my elasticsearch nested objects by using NEST c# client. My index name is people and my type is person in which the car field is nested.

This is my class:

using Nest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WebApplication5
{
public class person
{
    public int id { get; set; }
    public String fname  { get; set; }
    public String mname { get; set; }
    public String lname { get; set; }
    public String houseno { get; set; }
    [ElasticProperty(Type = FieldType.Nested)]
    public IList<NestedType> car { get; set; } 
    public class NestedType
    {
        public String carname { get; set; }
        public int car_no { get; set; }
        public String color { get; set; }
    }
}

} 

Now my web application looks something like this:

using Elasticsearch.Net;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Configuration;
using Nest;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication5
{
public partial class WebForm1 : System.Web.UI.Page
{
    Stopwatch stopwatch = new Stopwatch();
    Uri node;
    ConnectionSettings settings;
    ElasticClient client;
    IList<person> list;
    protected void Page_Load(object sender, EventArgs e)
    {
        node = new Uri("http://localhost:9200");
        settings = new ConnectionSettings(node, defaultIndex:   "people");
        client = new ElasticClient(settings);
        list = new List<person>();

    }

    public IList<person> Search(ref long totalResult, int from,  int size, string searchKeyword)
    {
        list.Clear();
        stopwatch.Start();

        try
        {

            var result = client.Search<person> /*   */


  stopwatch.Start();
  totalResult = result.Total;
  list = result.Hits.Select(t => t.Source).ToList<person>();
  }
        catch (Exception ex)
        {
            string msg = ex.Message;
        }
 return 
            list;
    }
 protected void TextBox1_TextChanged(object sender, EventArgs e)
    {
        long totalResult = 0;
        IList<person> List = new List<person>();
        List = Search(ref totalResult, 0, 1000,TextBox1.Text);
        Label1.Text = "" + List.Count + " result(s)"+ " in "+ stopwatch.Elapsed.TotalSeconds+"seconds";
        GridView1.DataSource = List;
        GridView1.DataBind();
        List.Clear();

    }
  }
}

Here searchKeyword is the the word that i need to search.
I enter it in a search box(webform) in the browser.
The searchKeyword may contain any value which needs to be matched to the value of any field in my table person.
If the searchKeyword matches with a nested document then the exact nested document should be returned.
I don't know there is something wrong with my nested query or i actually can't figure out how to use a nested query to do this. Everytime i run the application and search for a value which is actually present in the table,i get zero results. Please help me figure out how to write the query to search in the nested fields and help me in checking if i have written the class correctly. The query part is Marked as /* */. Can anyone help me resolve this?

UPDATE This is my index mapping :

POST- people/person/_mapping
{
"person":{
"properties":{
  "car": {
    "type": "nested"
    }
  }
 }
}

And my records look like this:

POST-people/person
{
"id":1, 
"fname":"aditi",  
"mname":"ananya", 
"lname":"xyz", 
"houseno":"abc",
"car":
 [
  {
   "carname":"fiat",
   "carno":1234,
   "color":"white"
  },

  {
   "carname":"maruti",
   "carno":5678,
   "color":"silver"
  }
 ]
}


POST-people/person
{
"id":2, 
"fname":"robin",  
"mname":"kumar", 
"lname":"sharma", 
"houseno":"efg",
"car":
  [
   {
      "carname":"audi",
      "carno":4321,
      "color":"black"
   },

   {
      "carname":"honda",
      "carno":8765,
      "color":"red"
   },

   {
      "carname":"merecedez",
      "carno":0101,
      "color":"purple"
   }
 ]
}

Which means i have 2 records.

SECOND UPDATE
I tried this query and it works fine. Though this is not my final query.

POST-people/person/_search
{
"_source":false,
"query": {
"filtered": {
  "query": {"match_all": {}},
  "filter": {
    "nested": {
      "path": "car",
      "query":{
        "filtered": {
          "query": { "match_all": {}},
          "filter": {
            "and": [
              {"term": {"car.carname":"mercedez"}},
              {"term": {"car.color":"purple"}}
            ]
          }
        }
      },
"inner_hits":{}
    }
  }
 }
}
}

THIRD UPDATE
Ok, so now In this case my final query should be :

{"_source":false,
"query": {
 "filtered": {
  "query": {"match_all": {}},
  "filter": {
    "nested": {
      "path": "car",
      "filter": {
        "term": {
          "car.carname": "audi"
        }
      }, 
      "inner_hits" : {}
    }
   }
  }
 }
}

How do i write this query in .net?
Here,the "_source" and "inner_hits" are important for me because i want to return only the matching nested document and nothing else(i.e. i want to return only the matched nested document and not the other nested documents).
So, can you please help me write the corresponding search query for this?
Moreover, here i'm matching in the car.carname field, but i want my application should be able to match for all other sub-fields of the car field like car.carno and car.color and even for all the other top level fields like id, fname etc.

FOURTH UPDATE
Here i have written the search query in .net for my final query that i had mentioned my in THIRD UPDATE (please look into my third update).
Can you please check if it is correct? The corresponding final query in .net which i have written is:

(s => s
 .Source(false)
   .Query(query => query.Filtered(filtered => filtered
    .Query(q => q.MatchAll())
      .Filter(f => f.Nested(nf => nf
        .InnerHits()
        .Path(p => p.car)
        .Query(qq => qq.Match(m => m.OnField(g=>g.car.First().carname).Query("audi"))))))));  

This is what i have written by studying your queries(Thanx a lot for that :) ). Do check this and tell me if there is anything wrong. But yeah,i'm still unable to retrieve any results.

Answer

Rob picture Rob · Jun 23, 2015

Try to use nested query.

Let me index some data for test purpose:

client.Index(new Person
{
    Id = 1, 
    Car = new List<NestedType> {new NestedType {Carname = "car1"}}
});
client.Index(new Person
{
    Id = 2,
    Car = new List<NestedType> {new NestedType {Carname = "car1"}, new NestedType {Carname = "car2"}}
});
client.Index(new Person
{
    Id = 3,
    Car = new List<NestedType> { new NestedType {Carname = "car2"}}
});

client.Refresh();

Now, try this nested query:

var searchResponse = client.Search<Person>(s =>
    s.Query(q => q
        .Nested(n => n
            .Path(p => p.Car)
            .Query(qq => qq.Match(m => m.OnField(f => f.Car.First().Carname).Query("car2"))))));

Search result:

{
   "took": 10,
   "timed_out": false,
   "_shards": {..},
   "hits": {
      "total": 2,
      "max_score": 1.4054651,
      "hits": [
         {
            "_index": "my_index",
            "_type": "person",
            "_id": "2",
            "_score": 1.4054651,
            "_source": {
               "id": 2,
               "car": [
                  {
                     "carname": "car1",
                     "carNo": 0
                  },
                  {
                     "carname": "car2",
                     "carNo": 0
                  }
               ]
            }
         },
         {
            "_index": "my_index",
            "_type": "person",
            "_id": "3",
            "_score": 1,
            "_source": {
               "id": 3,
               "car": [
                  {
                     "carname": "car2",
                     "carNo": 0
                  }
               ]
            }
         }
      ]
   }
}

Hope it helps you.

UPDATE

Including your formatting:

var searchResponse = client.Search<person>(s =>
    s.Query(q => q
        .Nested(n => n
            .Path(p => p.car)
            .Query(qq => qq.Match(m => m.OnField(f => f.car.First().carname).Query("car2"))))));

UPDATE2

Based on your update, try this query:

var searchResponse = client.Search<Person>(s => s
    .Query(query => query.Filtered(filtered => filtered
        .Query(q => q.MatchAll())
        .Filter(f => f.Nested(nf => nf
            .Path(p => p.Car)
            .Filter(filter => filter
                .And(
                    f1 => f1.Term(t => t.Car.First().Carname, "audi"),
                    f2 => f2.Term(t => t.Car.First().Color, "purple"))) 
            )))));

which produces this query to elasticsearch:

{
  "query": {
    "filtered": {
      "query": {
        "match_all": {}
      },
      "filter": {
        "nested": {
          "filter": {
            "and": {
              "filters": [
                {
                  "term": {
                    "car.carname": "audi"
                  }
                },
                {
                  "term": {
                    "car.color": "purple"
                  }
                }
              ]
            }
          },
          "path": "car"
        }
      }
    }
  }
}

UPDATE3

With inner hits:

var searchResponse = client.Search<Person>(s => s
    .Source(false)
    .Query(query => query.Filtered(filtered => filtered
        .Query(q => q.MatchAll())
        .Filter(f => f.Nested(nf => nf
            .InnerHits()
            .Path(p => p.Car)
            .Filter(filter => filter
                .And(
                    f1 => f1.Term(t => t.Car.First().Carname, "audi"),
                    f2 => f2.Term(t => t.Car.First().Color, "purple")))
            )))));

Note: there was bug in elasticsearch 1.5.0 regarding inner hits and filter. Have a look. Let me know if you need help with retrieving inner hits.