Reputation: 19993
Short version:
I would like to write an elastic search query using Nest to get the full indexed items (ContentIndexables
in my case as my custom type) which have been indexed. The query is subject to a term query of [some string] + * (i.e. String.StartsWith() where [some string] may or may not contain spaces.
This is different to CompletionSuggester
since I need to retrieve the full object and not string suggestions.
What I've tried so far:
When I query for a text without spaces, the desired output is returned using the code below. If my search term however contains spaces, it doesn't return the expected results.
Here's how I search the fields:
var searchResults = _client.Search<ContentIndexable>(
body =>
body
.Index(indexName)
.Query(
query =>
query.QueryString(
qs => qs.
OnFields(f => f.Title, f => f.TextContent)
.Query(searchTerm + "*"))));
And this is a unit test that demonstrates how to reproduce the problem:
indexService.IndexUserItemsSync(testGuid, IndexType.submission, new ContentIndexable
{
ContentId = Guid.NewGuid(),
TextContent = "Some description",
Title = "title"
});
indexService.IndexUserItemsSync(testGuid, IndexType.submission, new ContentIndexable
{
ContentId = Guid.NewGuid(),
TextContent = "Some description",
Title = "title that is long"
});
indexService.IndexUserItemsSync(testGuid, IndexType.submission, new ContentIndexable
{
ContentId = Guid.NewGuid(),
TextContent = "Some description",
Title = "title that likes"
});
indexService.IndexUserItemsSync(testGuid, IndexType.submission, new ContentIndexable
{
ContentId = Guid.NewGuid(),
TextContent = "Some description",
Title = "titlethat"
});
var searchResult = indexService.SearchUserItems(testGuid, IndexType.submission, 10, "title");
Assert.IsNotNull(searchResult);
// this one works
Assert.AreEqual(4, searchResult.Count());
var searchResult2 = indexService.SearchUserItems(testGuid, IndexType.submission, 10, "title that");
Assert.IsNotNull(searchResult2);
// this one does not!!! searchREsult2.Count() evaluates to 0
Assert.AreEqual(2, searchResult2.Count());
As you can see, then I enter "title that", the search comes back empty instead of the two rows I would expect to return.
Update: Some more information: I create an index on my type ContentIndexable:
public class ContentIndexable : IIndexable
{
public Guid ContentId { get; set; }
public string Title { get; set; }
public string TextContent { get; set; }
}
With this code:
_client.CreateIndex(
indexName,
descriptor =>
descriptor.AddMapping<ContentIndexable>(
m => m.Properties(
p => p.Completion(s => s
.Name(n => n.Title)
.IndexAnalyzer("standard")
.SearchAnalyzer("standard")
.MaxInputLength(30)
.Payloads()
.PreserveSeparators()
.PreservePositionIncrements())
.Completion(s => s.Name(n => n.TextContent)
.IndexAnalyzer("standard")
.SearchAnalyzer("standard")
.MaxInputLength(50)
.Payloads()
.PreserveSeparators()
.PreservePositionIncrements())
)));
I even tried to escape the whitespace both when I index or when I query with string.Replace(" ", @"\ ")
but that didn't help.
Changing the search type to wild card didn't help either:
var searchResults = _client.Search<ContentIndexable>(
body =>
body
.Index(indexName)
.Query(
query => query.Wildcard(qd => qd.OnField(f => f.Title).Value(searchTerm + "*"))));
Does anyone know what I'm doing wrong?
Please note that my CompletionSuggester
version works with spaces but unfortunately only returns strings. I need to get the complete item out in order to fetch the ContentId
. MY CompletionSuggester implementation:
public IEnumerable<string> GetAutoCompleteSuggestions(Guid userId, IndexType indexType, int size, string searchTerm)
{
string indexName = getIndexName(indexType, userId);
var result = _client.Search<ContentIndexable>(
body => body.Index(indexName)
.SuggestCompletion("content-suggest" + Guid.NewGuid(),
descriptor => descriptor
.OnField(t => t.Title)
.Text(searchTerm)
.Size(size)));
if (result.Suggest == null)
{
return new List<string>();
}
return (from suggest in result.Suggest
from value in suggest.Value
from options in value.Options
select options.Text).Take(size);
}
I know I can take the suggestions, get the full value out (which will result in the two items I'm expecting) and then do a full term match using my first method but that requires 2 separate calls into ElasticSearch (one for complete suggestor and the second one for the term query) but ideally I would like to do it without the round trip if possible.
Many thanks in advance,
Upvotes: 5
Views: 8182
Reputation: 9979
This is example how you can deal with your problem for Title
field.
Change your mapping to something like(or use MultiField, but I couldn't find option to map field as string and completion in the same time):
client.CreateIndex(indexName, i => i
.AddMapping<ContentIndexable>(m => m
.Properties(
ps => ps
.Completion(c => c.Name("title.completion")
.IndexAnalyzer("standard")
.SearchAnalyzer("standard")
.MaxInputLength(30)
.Payloads()
.PreserveSeparators()
.PreservePositionIncrements())
.String(s => s.Name(x => x.Title).CopyTo("title.completion")))));
Change your SuggestCompletion
to
var result = client.Search<ContentIndexable>(body => body
.Index(indexName)
.SuggestCompletion("content-suggest" + Guid.NewGuid(),
descriptor => descriptor
.OnField(t => t.Title.Suffix("completion"))
.Text("title")
.Size(10)));
and QueryString
to
var searchResponse = client.Search<ContentIndexable>(body => body
.Index(indexName)
.Query(query => query
.QueryString(
qs => qs
.OnFields(f => f.Title.Suffix("completion"))
.Query("title tha" + "*")
.MinimumShouldMatchPercentage(100))));
Problem with this solution is fact that we are storing data twice for Title
field. This is why I mentioned earlier that would be great to use MultiField but I wasn't able to do this with the NEST
.
Hope this helps.
Upvotes: 4