I'm having trouble figuring out how to index and search nested object.
I want to be able to search nested objects and return the parents - only the parents, without the list of Remarks, but I would like highlights from the remarks returned if possible.
My models:
[DataContract]
[ElasticsearchType(IdProperty = "CustomerId", Name = "CustomerSearchResult")]
public class SearchResult
{
    [DataMember]
    [String(Index = FieldIndexOption.NotAnalyzed)]
    public int CustomerId { get; set; }
    ...
    [Nested]
    [DataMember]
    public List<RemarkForSearch> Remarks { get; set; }
}
[ElasticsearchType(IdProperty = "RemarkId", Name = "RemarkForSearch")]
public class RemarkForSearch
{
    [DataMember]
    public int RemarkId { get; set; }
    [DataMember]
    public int CustomerId { get; set; }
    [DataMember]
    public string RemarkText { get; set; }
}
Index creation:
var customerSearchIdxDesc = new CreateIndexDescriptor(Constants.ElasticSearch.CustomerSearchIndexName)
    .Settings(f =>
        f.Analysis(analysis => analysis
                .CharFilters(cf => cf
                    .PatternReplace(Constants.ElasticSearch.FilterNames.RemoveNonAlphaNumeric, pr => pr
                        .Pattern(@"[^a-zA-Z\d]") // match all non alpha numeric
                        .Replacement(string.Empty)
                    )
                )
               .TokenFilters(tf => tf
                    .NGram(Constants.ElasticSearch.FilterNames.NGramFilter, fs => fs
                        .MinGram(1)
                        .MaxGram(20)
                    )
                )
                .Analyzers(analyzers => analyzers
                    .Custom(Constants.ElasticSearch.AnalyzerNames.NGramAnalyzer, a => a
                        .Filters("lowercase", "asciifolding", Constants.ElasticSearch.FilterNames.NGramFilter)
                        .Tokenizer(Constants.ElasticSearch.TokenizerNames.WhitespaceTokenizer)
                    )
                    .Custom(Constants.ElasticSearch.AnalyzerNames.WhitespaceAnalyzer, a => a
                        .Filters("lowercase", "asciifolding")
                        .Tokenizer(Constants.ElasticSearch.TokenizerNames.WhitespaceTokenizer)
                    )
                    .Custom(Constants.ElasticSearch.AnalyzerNames.FuzzyAnalyzer, a => a
                        .Filters("lowercase", "asciifolding")
                        //.CharFilters(Constants.ElasticSearch.FilterNames.RemoveNonAlphaNumeric)
                        .Tokenizer(Constants.ElasticSearch.TokenizerNames.NGramTokenizer)
                    )
                )
                .Tokenizers(tokenizers => tokenizers
                    .NGram(Constants.ElasticSearch.TokenizerNames.NGramTokenizer, t => t
                        .MinGram(1)
                        .MaxGram(20)
                        //.TokenChars(TokenChar.Letter, TokenChar.Digit)
                    )
                    .Whitespace(Constants.ElasticSearch.TokenizerNames.WhitespaceTokenizer)
                )
        )
    )
    .Mappings(ms => ms
        .Map<ServiceModel.DtoTypes.Customer.SearchResult>(m => m
            .AutoMap()
            .AllField(s => s
                .Analyzer(Constants.ElasticSearch.AnalyzerNames.NGramAnalyzer)
                .SearchAnalyzer(Constants.ElasticSearch.AnalyzerNames.WhitespaceAnalyzer)
            )
            .Properties(p => p
                .String(n => n
                    .Name(c => c.ContactName)
                    .Index(FieldIndexOption.NotAnalyzed)
                    .CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName))
                )
                .String(n => n
                    .Name(c => c.CustomerName)
                    .Index(FieldIndexOption.NotAnalyzed)
                    .CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName))
                )
                .String(n => n
                    .Name(c => c.City)
                    .Index(FieldIndexOption.NotAnalyzed)
                    .CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName))
                )
                .String(n => n
                    .Name(c => c.StateAbbreviation)
                    .Index(FieldIndexOption.NotAnalyzed)
                    .CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName))
                )
                .String(n => n
                    .Name(c => c.PostalCode)
                    .Index(FieldIndexOption.NotAnalyzed)
                    .CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName))
                )
                .String(n => n
                    .Name(c => c.Country)
                    .Index(FieldIndexOption.NotAnalyzed)
                    .CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName)) 
                )
                .Number(n => n
                    .Name(c => c.AverageMonthlySales)
                    .Type(NumberType.Double)
                    .CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName))
                )
                .String(n => n
                    .Name(Constants.ElasticSearch.CombinedSearchFieldName)
                    .Index(FieldIndexOption.Analyzed)
                    .Analyzer(Constants.ElasticSearch.AnalyzerNames.FuzzyAnalyzer)
                    .SearchAnalyzer(Constants.ElasticSearch.AnalyzerNames.FuzzyAnalyzer)
                )
                .Nested<ServiceModel.DtoTypes.Customer.RemarkForSearch>(s => s
                    .Name(n => n.Remarks)
                    .AutoMap()
                )
            )
        )
    );
var response = client.CreateIndex(customerSearchIdxDesc);
Loading the index:
        var searchResults = Db.SqlList<DtoTypes.Customer.SearchResult>("EXEC [Customer].[RetrieveAllForSearch]");
        var remarkResults = Db.SqlList<DtoTypes.Customer.RemarkForSearch>("EXEC [Customer].[RetrieveAllSearchableRemarks]");
        foreach(var i in searchResults)
        {
            i.Remarks = remarkResults.Where(m => m.CustomerId == i.CustomerId).ToList();
        }
        var settings = new ConnectionSettings(Constants.ElasticSearch.Node);
        var client = new ElasticClient(settings);
        // Flush the index
        var flushResponse = client.Flush(Constants.ElasticSearch.CustomerSearchIndexName);
        // Refresh index
        var indexResponse = client.IndexMany(searchResults, Constants.ElasticSearch.CustomerSearchIndexName);
Querying the Index:
var searchDescriptor = new SearchDescriptor<DtoTypes.Customer.SearchResult>()
    .From(0)
    .Take(Constants.ElasticSearch.MaxResults)
    .Query(q => q
        .Nested(c => c
            .Path(p => p.Remarks)
            .Query(nq => nq
                .Match(m => m
                    .Query(query)
                    .Field("remarks.remarktext")
                )
            )
        )
    );
response = client.Search<DtoTypes.Customer.SearchResult>(searchDescriptor);
I don't know if I'm bulk loading the index properly and if its smart enough to know that the Remarks property is a nested property and to load those as well.
The search has no errors, but I get no results.
The search query is generating this json, which from what I can tell is OK:
{
  "from": 0,
  "size": 100,
  "query": {
    "nested": {
      "query": {
        "match": {
          "remarks.remarktext": {
            "query": "test"
          }
        }
      },
      "path": "remarks"
    }
  }
}
I do see the remark data when looking at json using a query string http://127.0.0.1:9200/customersearch/_search
 
    