I have a requirement to provide suggestions which is working fine but I also need to filter the suggestions by another field in the document.
Is this possible to achieve? As long as I have figured out, Elasticsearch can not do this. Any alternative ideas?
public async Task<ISuggestResponse> Suggest(string index, string projectId, string field, string text)
{
var suggestResponse = await _client.SuggestAsync<TDocument>(s => s
.Index(index)
.Completion("suggest", c => c
.Text(text)
.Context(con => con.Add("projectId", projectId))
.Field(field)
.Size(20)
)
);
return suggestResponse;
}
-----------Update--------------------
ElasticsearchConfig.cs
client.Map<Component>(d => d
.Properties(props => props
.String(s => s
.Name("name"))
.Completion(c => c
.Name("componentSuggestion")
.Analyzer("simple")
.SearchAnalyzer("simple")
.Context(context => context
.Category("projectId", cat => cat
.Field(field => field.ProjectId)))
.Payloads()))
.Properties(props => props.String(s => s.Name("id").NotAnalyzed()))
.Properties(props => props.String(s => s.Name("projectId").NotAnalyzed())));
The Context Suggester extends the Completion Suggester to provide an element of basic filtering either on a Category or Geolocation. This may be sufficient for your purposes on it's own.
An alternative approach that you may want to take is to use the Context Suggester to provide search-as-you-type suggestions, indexing the id of each document within the payload of the completion type mapping; then use the ids returned within the payloads to search for documents, applying your additional filtering at this point and return only the document ids that match the filtering. Finally, use these document ids to get the suggestions from the original suggest response.
EDIT:
Here's a complete example of working with the Context Suggester
void Main()
{
var componentsIndex = "components";
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool)
// use component index when working with
// Component Poco type
.InferMappingFor<Component>(m => m
.IndexName(componentsIndex)
);
var client = new ElasticClient(settings);
// make this example repeatable, so delete index if
// it already exists
if (client.IndexExists(componentsIndex).Exists)
client.DeleteIndex(componentsIndex);
// for example purposes, only use one shard and no replicas
// NOT RECOMMENDED FOR PRODUCTION
client.CreateIndex(componentsIndex, c => c
.Settings(s => s
.NumberOfShards(1)
.NumberOfReplicas(0)
)
);
client.Map<Component>(d => d
.Index(componentsIndex)
// infer mapping of fields from property of the POCO.
// This means we don't need to explicitly specify all
// mappings in .Properties()
.AutoMap()
// Now, override any inferred mappings from automapping
.Properties(props => props
.Completion(c => c
.Name(n => n.ComponentSuggestion)
.Context(context => context
.Category("projectId", cat => cat
.Field(field => field.ProjectId)
)
)
.Payloads()
)
.String(s => s
.Name(n => n.Id)
.NotAnalyzed()
)
.String(s => s
.Name(n => n.ProjectId)
.NotAnalyzed()
)
)
);
var components = new[] {
new Component
{
Id = "1",
Name = "Component Name 1",
ComponentSuggestion = new CompletionField<object>
{
Input = new [] { "Component Name 1" },
Output = "Component Name 1"
},
ProjectId = "project_id"
},
new Component
{
Id = "2",
Name = "Component Name 2",
ComponentSuggestion = new CompletionField<object>
{
Input = new [] { "Component Name 2" },
Output = "Component Name 2"
},
ProjectId = "project_id_2"
}
};
// index some components with different project ids
client.IndexMany(components);
// refresh the index to make the newly indexed documents available for
// search. Useful for demo purposes but,
// TRY TO AVOID CALLING REFRESH IN PRODUCTION
client.Refresh(componentsIndex);
var projectId = "project_id";
var suggestResponse = client.Suggest<Component>(s => s
.Index(componentsIndex)
.Completion("suggester", c => c
.Field(f => f.ComponentSuggestion)
.Text("Compon")
.Context(con => con.Add("projectId", projectId))
.Size(20)
)
);
foreach (var suggestion in suggestResponse.Suggestions["suggester"].SelectMany(s => s.Options))
{
Console.WriteLine(suggestion.Text);
}
}
public class Component
{
public string Id { get; set; }
public string Name { get; set; }
public string ProjectId { get; set; }
public CompletionField<object> ComponentSuggestion { get; set; }
}
This yields only one suggestion, Component Name 1
, based on the context of projectId
being "project_id"
Instead of the completion suggester, you should be using the context suggester whose goal is to allow you to specify additional category or geo-location context for your completion suggester.
If I'm not mistaken, NEST provides the context suggester as part of the completion suggester through the Context
property.
public async Task<ISuggestResponse> Suggest(string index, string field, string text)
{
var suggestResponse = await _client.SuggestAsync<TDocument>(s => s
.Index(index)
.Completion("suggest", c => c
.Text(text)
.Context(con => con.Add("your_field", "text"))
.Field(field)
.Size(20)
)
);
return suggestResponse;
}
You also need to change the completion field in your mapping in order to declare the context.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With