3
votes

How to list a facet based on a property of a nested field using Mpdreamz/NEST Elasticsearch client?

I checked the Nest documentation, but it's not clear how to do it.

This is the code that I tried:

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

namespace Demo
{
    class Program
    {
        public class Movie
        {
            public int Id { get; set; }
            public string Title { get; set; }
            public string Description { get; set; }
            [ElasticProperty(Index = FieldIndexOption.analyzed, Type = FieldType.nested)]
            public List<Genre> Genres { get; set; }
            public int Year { get; set; }
        }

        public class Genre
        {
            //        public int Id { get; set; }
            [ElasticProperty(Index = FieldIndexOption.analyzed)]
            public string GenreTitle { get; set; }
        }

        static void Main(string[] args)
        {
            var setting = new ConnectionSettings("localhost", 9200);
            setting.SetDefaultIndex("default_index");
            var client = new ElasticClient(setting);

            // delete previous index with documents
            client.DeleteIndex<Movie>();

            // put documents to the index
            var genres = new List<Genre>();
            for (var i = 0; i < 100; i++)
                genres.Add(new Genre { GenreTitle = string.Format("Genre {0}", i) });
            for (var i = 0; i < 1000; i++)
            {
                client.Index(new Movie
                {
                    Id = i,
                    Description = string.Format("Some movie description {0}", i),
                    Title = string.Format("Movie Title {0}", i),
                    Year = 1980 + (i % 10),
                    Genres = genres.OrderBy(x => Guid.NewGuid()).Take(10).ToList()
                });
            }

            // query with facet on nested field property genres.genreTitle
            var queryResults = client.Search<Movie>(x => x
                    .From(0)
                    .Size(10)
                    .MatchAll()
                    .FacetTerm(t => t
                        .OnField(f => f.Year)
                        .Size(30))
                    .FacetTerm(t => t
                        .Size(5)
                        .OnField(f => f.Genres.Select(f1 => f1.GenreTitle) )
                    )
            );

            var yearFacetItems = queryResults.FacetItems<FacetItem>(p => p.Year);
            foreach (var item in yearFacetItems)
            {
                var termItem = item as TermItem;
                Console.WriteLine(string.Format("{0} ({1})", termItem.Term, termItem.Count));
            }
            /* Returns:
            1989 (90)
            1988 (90)
            1986 (90)
            1984 (90)
            1983 (90)
            1981 (90)
            1980 (90)
            1987 (89)
            1982 (89)
            1985 (88)
            and it's fine! */

            var genresFacetItems = queryResults.FacetItems<FacetItem>(p => p.Genres.Select(f => f.GenreTitle));
            foreach (var item in genresFacetItems)
            {
                var termItem = item as TermItem;
                Console.WriteLine(string.Format("{0} ({1})", termItem.Term, termItem.Count));
            }
            /* Return soemthing:
            genre (842)
            98 (47)
            51 (30)
            24 (29)
            46 (28)
            and it's BAD! 
            I expect the Genre Title to be listed as 
            string, not as some strange integer */
        }
    }
}

As result of facet I get:

  • genre (842)
  • 98 (47)
  • 51 (30)
  • 24 (29)
  • 46 (28)

While I expect to get something like:

  • Genre 1 (842)
  • Genre 2 (47)
  • Genre 3 (30)
  • Genre 4 (29)
  • Genre 5 (28)

What do I do wrong? Where to check the right way of using nested fields in Nest and facets on them?

Thank you.

UPDATE 1:

I found it has something to do with tokenizer/analyzer. If genre name is without spaces or dashes - everything works fine. I also tried not-analyzed index attribute

[ElasticProperty(Index = FieldIndexOption.not_analyzed)]
public string GenreTitle { get; set; }

but it didn't help

Update 2: I added fluent index mapping instead of annotations right after previous index delete like:

var settings = new IndexSettings();
            var typeMapping = new TypeMapping("movies");
            var type = new TypeMappingProperty
            {
                Type = "string",
                Index = "not_analyzed",
                Boost = 2.0
                // Many more options available
            };
            typeMapping.Properties = new Dictionary<string, TypeMappingProperty>();
            typeMapping.Properties.Add("genres.genreTitle", type);
            settings.Mappings.Add(typeMapping);
            client.CreateIndex("default_index", settings);

Now not sure what was wrong with annotations. Is there any additional config needed to use annotations for index settings?

1
I'm having the exact same problem... I can't even figure out what this number stands for...Udi

1 Answers

10
votes

Hi Author of NEST here,

If you use the annotations you need to manually call

var createIndex = client.CreateIndex("default_index", new IndexSettings { });
client.Map<Movie>();

Before the first call to index. Nest wont apply the mapping on each index call since that would incur too much of overhead. Older versions of elasticsearch would just create the index if it doesn't exist and would not need the CreateIndex call.

Since you want to facet on a nested type you have to pass .Nested("genres") to the facet call.

The numbers you saw were actually the nested docids :)

Here's my modified program.cs that works:

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

namespace Demo
{
  class Program
  {
    public class Movie
    {
      public int Id { get; set; }
      public string Title { get; set; }
      public string Description { get; set; }
      [ElasticProperty(Type=FieldType.nested)]
      public List<Genre> Genres { get; set; }
      public int Year { get; set; }
    }

    public class Genre
    {
      //        public int Id { get; set; }
      [ElasticProperty(Index = FieldIndexOption.not_analyzed)]
      public string GenreTitle { get; set; }
    }

    static void Main(string[] args)
    {
      var setting = new ConnectionSettings("localhost", 9200);
      setting.SetDefaultIndex("default_index");
      var client = new ElasticClient(setting);

      // delete previous index with documents
      client.DeleteIndex<Movie>();

      var createIndexResult = client.CreateIndex("default_index", new IndexSettings { });
      var mapResult = client.Map<Movie>();

      // put documents to the index
      var genres = new List<Genre>();
      for (var i = 0; i < 100; i++)
        genres.Add(new Genre { GenreTitle = string.Format("Genre {0}", i) });
      for (var i = 0; i < 1000; i++)
      {
        client.Index(new Movie
        {
          Id = i,
          Description = string.Format("Some movie description {0}", i),
          Title = string.Format("Movie Title {0}", i),
          Year = 1980 + (i % 10),
          Genres = genres.OrderBy(x => Guid.NewGuid()).Take(10).ToList()
        });
      }

      // query with facet on nested field property genres.genreTitle
      var queryResults = client.Search<Movie>(x => x
              .From(0)
              .Size(10)
              .MatchAll()
              .FacetTerm(t => t
                  .OnField(f => f.Year)
                  .Size(30))
              .FacetTerm(t => t
                  .Size(5)
                  .OnField(f => f.Genres.Select(f1 => f1.GenreTitle))
                  .Nested("genres")
              )
      );

      var yearFacetItems = queryResults.FacetItems<FacetItem>(p => p.Year);
      foreach (var item in yearFacetItems)
      {
        var termItem = item as TermItem;
        Console.WriteLine(string.Format("{0} ({1})", termItem.Term, termItem.Count));
      }

      var genresFacetItems = queryResults.FacetItems<FacetItem>(p => p.Genres.Select(f => f.GenreTitle));
      foreach (var item in genresFacetItems)
      {
        var termItem = item as TermItem;
        Console.WriteLine(string.Format("{0} ({1})", termItem.Term, termItem.Count));
      }

    }
  }
}