Search code examples
c#elasticsearchnest

Nest SuggestCompletion usage, throws 'is not a completion suggest field' exception


I'm a complete beginner to elasticsearch and I have been trying to use elasticsearch's completion suggester using Nest for auto-complete on a property.

Here is my mapping (as mentioned here: ):

var createResult = client.CreateIndex(indexName, index => index                
            .AddMapping<Contact>(tmd => tmd
                .Properties(props => props
                    .Completion(s =>
                        s.Name(p => p.CompanyName.Suffix("completion"))
                        .IndexAnalyzer("standard")
                        .SearchAnalyzer("standard")
                        .MaxInputLength(20)
                        .Payloads()
                        .PreservePositionIncrements()
                        .PreserveSeparators())                        
                )
            )
        );

var resultPerson = client.IndexMany(documents.OfType<Person>(), new SimpleBulkParameters { Refresh = true });
var resultCompany = client.IndexMany(documents.OfType<Company>(), new SimpleBulkParameters { Refresh = true });

And while indexing I'm just making use of IndexMany method and passing the IEnumerable<Contact> (Contact has a property by name CompanyName, Contact is an abstract class, both Person and Company are concrete implementations of it). The search throws an exception saying

ElasticSearchException[Field [companyName] is not a completion suggest field]

And the query looks like below:

SearchDescriptor<Contact> descriptor = new SearchDescriptor<Contact>();
descriptor = descriptor.SuggestCompletion("suggest", c => c.OnField(f => f.CompanyName).Text(q));

var result = getElasticClientInstance("contacts").Search<Contact>(body => descriptor);

string qe = result.ConnectionStatus.ToString();

What am I doing wrong here, I looked into Nest's tests on SuggestCompletion but not much help, meaning the test only depict on how to get suggestions but not on how to set index mappings for SuggestCompletion.

I also tried setting up edgeNgram tokenizer as mentioned in this post but, couldn't proceed there as well.

Any direction or an example on how to proceed would greatly help.


Solution

  • UPDATE

    You are try to create a property with the name "companyName.completion" but at that position its not valid and it will use the last token "completion". So its actually mapping a field called completion.... try changing the call to: .Name(p => p.CompanyName)

    Other observations

    You specify a mapping for the Contact but while indexing you use the Person and Company types.

    In elasticsearch terms you mapped:

    /index/contact/

    but your documents are going into:

    /index/person/ and /index/company

    NEST won't automatically map all implementation of a specific class and elasticsearch has no way of knowing the three are related.

    I would refactor the mapping to a method and call it for all the types involved.

     var createResult = client.CreateIndex(indexName, index => index                
          .AddMapping<Contact>(tmd => MapContactCompletionFields(tmd))
          .AddMapping<Person>(tmd => MapContactCompletionFields(tmd))
          .AddMapping<Company>(tmd => MapContactCompletionFields(tmd))   
      );
    
     private RootObjectMappingDescriptor<TContact>  MapContactCompletionFields<TContact>(
          RootObjectMappingDescriptor<TContact> tmd)
          where TContact : Contact
     {
          return  tmd.Properties(props => props
               .Completion(s => s
                    .Name(p => p.CompanyName.Suffix("completion"))
                    .IndexAnalyzer("standard")
                    .SearchAnalyzer("standard")
                    .MaxInputLength(20)
                    .Payloads()
                    .PreservePositionIncrements()
                    .PreserveSeparators()
               )                        
           );
     } 
    

    That method returns the descriptor so you can further chain on it.

    Then when you do a search for contacts:

    var result = getElasticClientInstance("contacts").Search<Contact>(
        body => descriptor
            .Types(typeof(Person), typeof(Company))
    );
    

    That types hint will cause the search to looking /index/person and /index/company and will know how to give you back a covariant list of documents.

    So you can do result.Documents.OfType<Person>() after the previous call.