How to iterate fields as per coming input fields in NEST queries? - c#

I written my NEST query like below
var searchResponse = await _elasticClient.SearchAsync<T>(s => s
.Index(indexName)
.Query(q => q
.Bool(b => b
.Should(
sh => sh.Prefix(pr => pr.Field(fieldNames[0]).Value(fieldValues[0])),
sh => sh.Prefix(pr => pr.Field(fieldNames[1]).Value(fieldValues[1]))
)
))
.Aggregations(ag=>ag.Cardinality(sumName,ca=>ca.Field(cardinalField)))
.Collapse(co=>co.Field(cardinalField))
).ConfigureAwait(false);
return searchResponse.Count;
}
I need to iterate below code
sh => sh.Prefix(pr => pr.Field(fieldNames[0]).Value(fieldValues[0])),
sh => sh.Prefix(pr => pr.Field(fieldNames[1]).Value(fieldValues[1]))
as per input array
I tried like below
private static QueryContainer MatchAny<T>(QueryContainerDescriptor<T> descriptor, Field[] fields, string value) where T : class
{
QueryContainer q = new QueryContainer();
for (int i=0;i<=fields.Length-1;i++)
{
q |= descriptor.Match(t => t.Field(fields[i]).Query(value));
}
return q;
}
var searchResponse = await _elasticClient.SearchAsync<T>(s => s
.Index(indexName)
.Query(q => q
.Bool(b => b
.Should(sh=> MatchAny(sh, fieldNames, fieldValue)
)
))
).ConfigureAwait(false);
but getting compile error :
cannot convert from 'string[]' to 'Nest.Field[]'
so how to iterate fields as per coming no of inputs in NEST queries ?

Related

How set default value for field that get from ElasticSearch using NEST

Using NEST I attempt set default value for IndexName field but it always is null
int month = startDate.Value.Date.Month;
var indexName = $"oem_catalog-0{month}.{startDate.Value.Date.Year}";
if (month < 10)
{
indexName = $"oem_catalog-0{month}.{startDate.Value.Date.Year}";
}
var scanResults = _elasticClient.Search<OemCatalogModel>(s => s.Index(indexName)
.Source(sf => sf
.Includes(i => i
.Fields(
f => f.Event,
f => f.MemberId,
f => f.IsInternalUser,
f => BuildTermQuery<OemCatalogModel, string>(c => c.IndexName, indexName),
f => f.IsMobile,
f => f.VinNumber,
f => f.Timestamp
)
)
)
.Query(q => q.Range(p =>
p.Field(f => f.Timestamp)
.GreaterThanOrEquals(startDate.Value.ToUnixTimeSeconds())
.LessThanOrEquals(endDate.Value.ToUnixTimeSeconds())
))
.Query(q => q.Match(m => m
.Field(f => f.Event)
.Query(eventName)
)).Size(10000).Scroll("10s"));
var scrolls = 0;
var results = this._elasticClient.Scroll<OemCatalogModel>("4s", scanResults.ScrollId);
while (results.Documents.Any())
{
yield return results.Documents;
results = _elasticClient.Scroll<OemCatalogModel>("4s", results.ScrollId);
scrolls++;
}
the BuildTermQuery loooks following
private static TermQuery BuildTermQuery<T, TProp>(Expression<Func<T, TProp>> fieldExpression, string value)
{
var query = new TermQuery
{
Field = new Field(fieldExpression),
Value = value
};
return query;
}
How set default value?

How to use Conditionals in NEST Queries Elasticsearch

I want to build a NEST query for Elasticsearch depending on the user input with an If Else statement. At the moment it only accepts one condition in the must part and the other one isn't added to the query.
The following code compiles to this http request:
{"from":0,"query":{"bool":{"must":[{"nested":{"path":"customer","query":{"term":{"customer.customerId":{"value":1}}}}}]}},"size":10}
as you can see the SearchPersonId isn't added into the must condition.
The search method:
private ISearchResponse<Country> GetFilteredResults(ElasticClient client, Query query)
{
var searchRequest = new SearchDescriptor<Country>();
searchRequest.From(0).Size(10).Query(q =>
{
q.Bool(b => b
.Must(mc =>
{
if (query.CustomerId != 0) mc.SearchCustomerId(query.CustomerId);
if (query.PersonId != 0) mc.SearchPersonId(query.PersonId);
return mc;
})
);
return q;
});
return client.Search<Country>(searchRequest);
}
The query methods:
public static class Helpers
{
public static QueryContainer SearchPersonId(this QueryContainerDescriptor<Country> container, string personId)
{
return container
.Nested(n => n
.Path(p => p.Person)
.Query(q => q
.Term(t => t
.Field(f => f.Person.PersonId).Value(personId))));
}
public static QueryContainer SearchCustomerId(this QueryContainerDescriptor<Country> container, string customerId)
{
return container
.Nested(n => n
.Path(p => p.Customer)
.Query(q => q
.Term(t => t
.Field(f => f.Customer.CustomerId).Value(customerId))));
}
}
One of the overloads of Must method accepts array of QueryContainer which can help you implement conditional logic
ISearchResponse<Country> GetFilteredResults(ElasticClient client, Query query)
{
var queryContainers = new List<QueryContainer>();
var descriptor = new QueryContainerDescriptor<Country>();
if (query.CustomerId != 0) queryContainers.Add(descriptor.SearchCustomerId(query.CustomerId));
if (query.PersonId != 0) queryContainers.Add(descriptor.SearchPersonId(query.PersonId));
var searchRequest = new SearchDescriptor<Country>();
searchRequest.From(0).Size(10).Query(q => q.Bool(b => b.Must(queryContainers.ToArray())));
return client.Search<Country>(searchRequest);
}

C# NEST nested filter to get values within the dateRange or NULL

I am developing something to show a pie chart of the users' age with slicing them as
0-17
18-34
35-44
44-54
55+
Not Available
here i am getting the ages based on the date range;
var aggaResonse = client.Search<JSModel>(a => a
.Size(0)
.Query(q => q.Bool(b => b.Must(m => m
.DateRange(date => date
.Field(p => p.CreatedDate)
.GreaterThanOrEquals(start)
.LessThanOrEquals(end)),
m =>
m.Term(t => t.Field(f => f.StepType.Suffix("keyword")).Value("User"))
)
))
.Aggregations(c => c.DateRange(nameof(AgeModel), x => x
.Field(f => f.BirthDate)
.Ranges(r => r.From(DateMath.Now.Subtract("17y")).To(DateMath.Now).Key(nameof(result.Years_0_17)),
r => r.From(DateMath.Now.Subtract("34y")).To(DateMath.Now.Subtract("18y")).Key(nameof(result.Years_18_34)),
r => r.From(DateMath.Now.Subtract("44y")).To(DateMath.Now.Subtract("35y")).Key(nameof(result.Years_35_44)),
r => r.From(DateMath.Now.Subtract("54y")).To(DateMath.Now.Subtract("45y")).Key(nameof(result.Years_45_54)),
r => r.From(DateMath.Now.Subtract("120y")).To(DateMath.Now.Subtract("55y")).Key(nameof(result.Years_55_Plus))
)
)
));
if (!aggaResonse.IsValid)
return result;
result.Years_0_17 = aggaResonse.Aggregations.Range(nameof(AgeModel)).Buckets.Single(c => c.Key == nameof(result.Years_0_17)).DocCount;
result.Years_18_34 = aggaResonse.Aggregations.Range(nameof(AgeModel)).Buckets.Single(c => c.Key == nameof(result.Years_18_34)).DocCount;
result.Years_35_44 = aggaResonse.Aggregations.Range(nameof(AgeModel)).Buckets.Single(c => c.Key == nameof(result.Years_35_44)).DocCount;
result.Years_45_54 = aggaResonse.Aggregations.Range(nameof(AgeModel)).Buckets.Single(c => c.Key == nameof(result.Years_45_54)).DocCount;
result.Years_55_Plus = aggaResonse.Aggregations.Range(nameof(AgeModel)).Buckets.Single(c => c.Key == nameof(result.Years_55_Plus)).DocCount;
return result;
what i need is to have a "Not Available" slice for users who has NULL as birthdate with mapping it as;
result.Not_Available = ....;
Any suggestions with following best practices for nested NEST aggs ?
I was thinking to run another search which i guess it's not the best practice.
After digging documentations too much, here is the solution i wrote;
I added a "Missing" attribute to the current aggregation;
&& c.Missing("DOBMissing", x => x.Field(f => f.BirthDate))
So it became like;
.Aggregations(c => c.DateRange(nameof(AgeModel), x => x
.Field(f => f.BirthDate)
.Ranges(r => r.From(DateMath.Now.Subtract("17y")).To(DateMath.Now).Key(nameof(result.Years_0_17)),
r => r.From(DateMath.Now.Subtract("34y")).To(DateMath.Now.Subtract("18y")).Key(nameof(result.Years_18_34)),
r => r.From(DateMath.Now.Subtract("44y")).To(DateMath.Now.Subtract("35y")).Key(nameof(result.Years_35_44)),
r => r.From(DateMath.Now.Subtract("54y")).To(DateMath.Now.Subtract("45y")).Key(nameof(result.Years_45_54)),
r => r.From(DateMath.Now.Subtract("120y")).To(DateMath.Now.Subtract("55y")).Key(nameof(result.Years_55_Plus))
)
) &&
c.Missing("DOBMissing", x => x.Field(f => f.BirthDate))
)
And i'd accessed the "missing" part of aggregation as following;
result.Not_Available = aggaResponse.Aggregations.Missing("DOBMissing").DocCount;

Query returning same customer

I have two records for the same customer but with different customerLastUpdated dates, there is a way to return only the most recent one?
var response = await this.client.SearchAsync<ElasticCustomer>(searchDescriptor => searchDescriptor
.AllTypes()
.Query(q => q.Bool(b => b
.Should(
sh => sh.Match(m => m.Field(f => f.CustomerName).Query(query)),
sh => sh.Wildcard(w => w.Field(f => f.MobileNumber.Suffix("keyword")).Value($"*{query}*")))));

Nest Elasticsearch, combining bool query of Must and Should

I want to filter a group of documents by Year, Format and Content.
n pseudo-SQL:
SELECT * FROM /test/document
WHERE
((format=f1|| format=f2||...|| format=fn) AND
(Year!=2013 AND Year!=2015) AND
(content like %c1% || content like %c2% ||...|| content like %cn%))
As you see, the number of formats and content items are dynamic and will be chosen by user.
So far, I figured out how to make dynamic query for each field separately and then combine them using bool query like the code below;
// For YEAR
QueryContainer qYear=null;
foreach (var year in years)
{
qYear |= new TermQuery() { Field = "year", Value = year };
}
// For FORMAT
var qformat = new List<QueryContainer>();
foreach (var format in txtDocs)
{
qformat.Add(Query<Document>.Match(m => m.OnField(p => p.Format).Query(format)));
}
// For CONTENT
var qc = new List<QueryContainer>();
qc.Add(Query<Document>.Match(m => m.OnField(p => p.File).Query(discpString).Boost(2)));
qc.Add(Query<Document>.Match(m => m.OnField(p => p.File).Query(roleString)));
qc.Add(Query<Document>.Match(m => m.OnField(p => p.File).Query(taskString)));
qc.Add(Query<Document>.Match(m => m.OnField(p => p.File).Query(sysString).Boost(2)));
//MY QUERY
var searchResults = client.Search<Document>(s => s.Fields(f => f.Title, f => f.Format, f => f.Year, f => f.DateCreated, f => f.Id, f => f.Path, f => f.Size, f => f.Firstwords).
Index(defaultIndex).From(0).Size(100).
Query(q => q.Bool(b => b.MustNot(qYear).Should(qformat.ToArray()).Should(qc.ToArray()))));
When I run this code, the results for year and content field is what I expect but other formats that are not in the filtered list are also included! I want it to just retrieve those documents with the selected formats.
Does anyone knows where is my mistake?
I could find where was my mistake!
I used Querycontainer for format in the same command as what I used for Year and then used Must in my query. Here is the changed part of the code:
// For FORMAT
QueryContainer qF=null;
foreach (var format in txtDocs)
{
qF |= new TermQuery()
{
Field = "format",
Value = format
};
}
//MY QUERY
var searchResults = client.Search<Document>(s => s
.Fields(
f => f.Title,
f => f.Format,
f => f.Year,
f => f.DateCreated,
f => f.Id,
f => f.Path,
f => f.Size,
f => f.Firstwords)
.Index(defaultIndex)
.From(0)
.Size(100)
.Query(q => q
.Bool(b => b.MustNot(qYear)
.Must(qF)
.Should(qc.ToArray())
)
);

Categories

Resources