I have the following document type in elastic :
public class ProductDto
{
public Int64 Id { get; set; }
public String Title{ get; set; }
public bool Visible { get; set; }
public IList<ProductSupplierDto> ProductSuppliers { get; set; }
}
public class ProductSupplierDto
{
public Int64 Id { get; set; }
public String Title{ get; set; }
public bool Enabled { get; set; }
}
how to write below linq query with Nest libray :
var products = db.products.where(p=> p.Visible
&& p.ProductSuppliers.Any(ps=>ps.Enabled)
).ToList();
in nest library i have the following query:
var baseQuery = Query<ProductDto>.Term(qt => qt.Field(f =>
f.Visible).Value(true));
how to add productsuppliers filter to baseQuery ?
I use this method for create index :
private async Task CreateIndexIfItDoesntExist<T>(string index) where T: class
{
if (!this.client.IndexExists(index).Exists)
{
var indexDescriptor = new CreateIndexDescriptor(index)
.Settings(x => x.NumberOfReplicas(0))
.Mappings(mappings => mappings
.Map<T>(m => m.AutoMap()));
await this.client.CreateIndexAsync(index, i => indexDescriptor);
// Max out the result window so you can have pagination for >100 pages
await this.client.UpdateIndexSettingsAsync(index, ixs => ixs
.IndexSettings(s => s
.Setting("max_result_window", int.MaxValue)));
}
}
and call like this :
await CreateIndexIfItDoesntExist<ProductDto>("products");
methods for index data:
private async Task<IndexResult> IndexDocuments<T>(T[] datas, string index) where T:class
{
int batchSize = 1000; // magic
int totalBatches = (int)Math.Ceiling((double)datas.Length / batchSize);
for (int i = 0; i < totalBatches; i++)
{
var response = await this.client.IndexManyAsync(datas.Skip(i * batchSize).Take(batchSize), index);
if (!response.IsValid)
{
return new IndexResult
{
IsValid = false,
ErrorReason = response.ServerError?.Error?.Reason,
Exception = response.OriginalException
};
}
else
{
Debug.WriteLine($"Successfully indexed batch {i + 1}");
}
}
return new IndexResult
{
IsValid = true
};
}
ProductSupplierDto
in ProductSuppliers
will be mapped as an object
type with automapping, so the following query will achieve what you're after
var client = new ElasticClient();
var searchResponse = client.Search<ProductDto>(s => s
.Query(q => +q
.Term(f => f.Visible, true) && +q
.Term(f => f.ProductSuppliers[0].Enabled, true)
)
);
This generates the following query
{
"query": {
"bool": {
"filter": [
{
"term": {
"visible": {
"value": true
}
}
},
{
"term": {
"productSuppliers.enabled": {
"value": true
}
}
}
]
}
}
}
A couple of points
bool
query filter
clause). Since a document either matches or it doesn't, a relevancy score for matching does not need to be calculated.f => f.ProductSuppliers[0].Enabled
is an expression to get the path to a field. It does not mean "get the value of Enabled
from the first item in ProductSuppliers
", but means "get the path to the Enabled
field of all items in the ProductSuppliers
property". the indexer into the collection here is only to be able to access the properties of the ProductSupplierDto
type.You may want to consider mapping ProductSuppliers
as a nested
type so that you would be able to query across properties of individual items in a ProductSuppliers
collection. With ProductSuppliers
mapped as a nested
type, the query would then be
var searchResponse = client.Search<ProductDto>(s => s
.Query(q => +q
.Term(f => f.Visible, true) && +q
.Nested(n => n
.Path(p => p.ProductSuppliers)
.Query(nq => nq
.Term(f => f.ProductSuppliers[0].Enabled, true)
)
)
)
);