NEST 2.x Terms Query cannot accept 2 arguments - elasticsearch

How NEST 1.x expression below could be rewritten to NEST 2.x or 5.x
var searchResult = _elasticClient.Search<SearchResult>(
request => request
.MinScore(0.7)
.Query(q =>
{
QueryContainer query = null;
query &= q.Terms<int>(t => t.Categories
.SelectMany(s => s.ChildCategories.Select(c => c.Id))
.ToArray(),
categories.Select(c => Convert.ToInt32(c)));
to accept List() which contains elements on what ids elastic search query should match
query &= q.Terms(c => c.Field(t => t.Categories.SelectMany(s => s.ChildCategories.Select(d => d.Id))));
This line will below complain about Terms has 1 parameter, but invoked with 2
query &= q.Terms(c => c.Field(t => t.Categories.SelectMany(s => s.ChildCategories.Select(d => d.Id))), new List<int> {1});
UPDATE:
The last example on elasticsearch documentation for 1.X contains field and
qff.Terms(p => p.Country, userInput.Countries) which I want to achieve in NEST 5.x or 2.x

Take a look at the Terms query documentation. A terms query needs a field that contains the term(s) to match and the collection of terms to match against.
field to match can be specified using .Field(), which can take anything from which a Field can be inferred, including a string or a Lambda expression.
values to match against can be specified using .Terms(), which is a collection of terms.
Given the following POCO
public class Project
{
public IEnumerable<string> Tags { get; set; }
}
A terms query on the tags field would be
var searchResponse = client.Search<Project>(s => s
.Query(q => q
.Terms(t => t
.Field(f => f.Tags)
.Terms("tag1", "tag2", "tag3")
)
)
);

Related

Query with '-' returns incorrect results in ElasticSearch NEST 7.x

Here is the query I use to search:
var response = await client.SearchAsync<MenuForElasticSearch>(searchDescriptor => searchDescriptor
.Query(queryContainerDescriptor => queryContainerDescriptor
.Bool(queryDescriptor => queryDescriptor
.Should(queryStringQuery => queryStringQuery.Match(match => match.Field(fld => fld.DisplayName).Query(query)),
queryStringQuery => queryStringQuery.Wildcard(wildcard => wildcard.Field(flds => flds.DisplayName).Value($"*{query}*")),
queryStringQuery => queryStringQuery.Fuzzy(fuzzy => fuzzy.Field(flds => flds.DisplayName).Value(query)))
)));
There are three documents with displayName = NPW-711, NPW-677 and NPW-777. When I search NPW-711 it returns all three documents.
Can adding DefaultOperator(Elasticsearch.Net.DefaultOperator.And) help? If yes, where it fits?
Match query with AND operator will give you what you are looking for
var results = await client.SearchAsync<Document>(s => s
.Query(q => q
.Match(m => m
.Field("name")
.Query(query)
.Operator(Operator.And))));
output:
Results for query "NPW-777": NPW-777
Results for query "NPW": NPW-711,NPW-677,NPW-777
Results for query "677": NPW-677
Hope that helps.

How to exclude certain values from elastic nest

I'm using NEST query to filter records from elastic.
The following query will filter records based on phrase and list of sourceIds. But I would like to exclude some documents from the result if their URL contains ideaArticles list.
var result = ElasticSearchClientConnection.Client.Search<T>(s => s
.Query(q => q.Match(p => p.Field(f => f.Body).Query(phrase))
&& q.Terms(p => p.Field(f => f.SourceId).Terms(sourceIds))
&& !q.Terms(p => p.Field(f => f.URL).Terms(ideaArticles))
).Take(take));
I resolved this issue with MatchPhrase. But since I had a list of phrases, I had to create a query on the fly.
var result =
ElasticSearchClientConnection.Client.Search<T>(s =>
s.Query(q => q.Match(p => p.Field(f => f.Body).Query(phrase))
&& q.Terms(p => p.Field(f => f.SourceId).Terms(sourceIds))
&& BuildMatchPhraseQueryContainer(q, ideaArticles)).Take(take));
and this is the method to create the query on the fly
private QueryContainer BuildMatchPhraseQueryContainer(QueryContainerDescriptor<T> qd, List<string> phrases)
{
QueryContainer queryContainer = new QueryContainer();
foreach (var phrase in phrases)
{
queryContainer &= !qd.MatchPhrase(m => m.Field(f => f.URL).Query(phrase));
}
return queryContainer;
}

ElasticSearch C# client (NEST): Filtering results with ES 5.5.0

This was my code in the earlier version of ES it used to work. After moving to ES 5.5. It has stopped working and it gives a compiler error.
Error: 'QueryStringQueryDescriptor' does not contain a definition for 'OnFields' and no extension method 'OnFields' accepting a first argument of type 'QueryStringQueryDescriptor'
Below is my code snippet...
public List<EmployeeInfo> SearchText2(string query, List<string> sendersList, int page = 0, int pageSize = 50)
{
try
{
var result = this.client.Search<EmployeeInfo>(s => s
.From(page * pageSize)
.Size(int.MaxValue)
.Query(q => q
.QueryString(qs => qs.Query(query).UseDisMax()
.OnFields(b => b.Subject)
.OnFields(b => b.Body)
))
.SortDescending(f => f.ReceivedTime)
.Filter(f => f.Terms(ak => ak.SenderName, sendersList))
);
...
// Some code here
}
Any tips on how to make this work will be great.
In latest version of Nest library there are some API changes
Instead of OnFields in QueryString you should use Fields
QueryString(qs => qs.Query(string.Empty).UseDisMax()
.Fields(descriptor => descriptor.Fields(b => b.Subject, b => b.Body))
))
Instead of SortDescending you should use Sort
.Sort(descriptor => descriptor.Field(f => f.ReceivedTime, SortOrder.Descending))
Also the filters are not available in elasticsearch starting from version 5 and you should use bool query with filter
Query(descriptor =>
descriptor.Bool(boolQuery =>
boolQuery
.Must(query => query.MatchAll())
.Filter(f => f.Terms(ak => ak.SenderName, sendersList)
)
)
)

NEST Aggregate similar to SQL Group By

This is my class when inserting to ES
public class BasicDoc
{
public string Name { get; set; }
public string Url { get; set; }
}
I managed successfully insert my document to ES using NEST. But I'm having trouble to do a aggregation. My goals is to have something similar to SQL Group By. What I did so far:
var response = elastic.Search<BasicDoc>(s => s
.Aggregations(a => a
.Terms("group_by_url", st => st
.Field(o => o.Url)
))
);
I tried to aggregate my document based on BasicDoc.Url. Say I have these in my ES:
/api/call1/v1
/api/call2/v1
/api/call1/v1
When I debug, I my Nest.BucketAggregate will have 4 Items key which is api,call1, call2 and v1. I was expecting only 2 which are /api/call1/v1 and /api/call2/v1. What I'm doing wrong?
You currently have analysis set up on your Url property which means that it will be tokenized by the standard analyzer and terms stored in the inverted index. If you need to be able to search on Uri and also need to aggregate on it, then you may consider mapping it as a multi_field where one field mapping analyzes it and another does not. Here's an example index creation with mapping
client.CreateIndex("index-name", c => c
.Mappings(m => m
.Map<BasicDoc>(mm => mm
.AutoMap()
.Properties(p => p
.String(s => s
.Name(n => n.Url)
.Fields(f => f
.String(ss => ss
.Name("raw")
.NotAnalyzed()
)
)
)
)
)
)
);
When you perform your aggregation, you can now use the Uri raw field
var response = client.Search<BasicDoc>(s => s
.Size(0)
.Aggregations(a => a
.Terms("group_by_url", st => st
.Field(o => o.Url.Suffix("raw"))
)
)
);

Elasticssearch Nest Synonyms 2.XX

I'm a newbie for elasticsearch and we are evaluate elasticsearch for our webstore. One important feature is the usage of synonyms. Unfortunately I'm not able to create a index with synonyms. Please can anybody help me how I can use the synonyms feature. I didn't find any sample for this feature and elasticsearch 2.xx. The goal should be if I search for Hills the entry of Royal will be find.
I use the following code:
private ElasticClient GetClient()
{
var node = new Uri(ES_URI);
var uri = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(uri).DefaultIndex("product");
var client = new ElasticClient(settings);
return client;
}
public void CreateSynonymIndex()
{
Product product = new Product()
{
Id = "2",
ProductName = "Royal",
Description = "Katzenfutter für Nierkranke"
};
var client = GetClient();
client.DeleteIndex("product");
var syn = new[] { "royal, hills => royal" };
ICreateIndexResponse respose = client.CreateIndex("product", c => c
.Mappings(mp => mp.Map<Product>(d => d.
Properties(p => p.String(n => n.Name(name => name.ProductName).Index(FieldIndexOption.Analyzed)))))
.Settings(s => s
.Analysis(an => an
.Tokenizers(at=>at.Pattern("synonymTokenizer",pa=>pa.Pattern("Test")))
.Analyzers(a=>a.Custom("synonymAnalyser",ca =>ca
.Tokenizer("synonymTokenizer")
.Filters(new List<string> { "synonym" })))
.TokenFilters(tf => tf
.Synonym("synonym", sy => sy.Synonyms(syn)
.Tokenizer("whitespace")
.IgnoreCase(true)))))
);
client.Index(product);
}
public void ES_Search()
{
var client = GetClient();
var response = client.Search<Product>(search => search
.Query(q => q.Bool(b => b
.Should(
// s => s.Match(m => m.Query("sometest").Field(f => f.ProductName).Boost(1.1)),
s => s.Match(m => m.Query("hills").Field(f => f.ProductName).Fuzziness(Fuzziness.EditDistance(1)))
))));
var response1 = client.Search<Product>(s => s.Query(q => q.Term(p => p.ProductName, "hills")));
}
Regards,
Dominik
You have created analyzer with synonyms, but you are not using it. You need to tell elasticsearch that ProductName field should use synonymAnalyser analyzer.
.Mappings(mp => mp.Map<Product>(d => d.
Properties(p => p.String(n => n
.Name(name => name.ProductName)
.Analyzer("synonymAnalyser")
.Index(FieldIndexOption.Analyzed)))))
I noticed few more things though:
remeber that document is not immediately available in elasticsearch after calling client.Index(..) method. It will take some miliseconds. Searching just right after indexing document, you may not find it. You can read more about it here
I don't know if you creat ElasticClient with default index, because you didn't share it. If not, you will have to specify it in your search calls e.g.
client.Search<Product>(s => s.Index("product")).
Hope that helps you.

Resources