How to combine multiple conditional query in one query

Hello, I'm new to using Elasticsearch. I'm using the .NET client version 8.12, I'm trying to send a query based on parameters. Only add the condition if the parameter value exists. Below is the code for the query:

public class ProductSearchDto
{
    public string? Term { get; set; }
    public string? CategoryId { get; set; }
    public int? PriceFrom { get; set; }
    public int? PriceTo { get; set; }
    public int? Stars { get; set; }
    public int Page { get; set; }
    public int PageSize { get; set; }
}

The index class:

public class ProductIndex 
{
    public string Id { get; set; }
    public decimal Price { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string CategoryId { get; set; }
    public int Stars { get; set; }
    public DateTime IndexedAt { get; set; } = DateTime.UtcNow;
}

Also, for the generation of the query below is the code that calls Elasticsearch:

public async Task<List<ProductIndex>> Search(ProductSearchDto searchDto)
        {
            QueryDescriptor<ProductIndex> query = new QueryDescriptor<ProductIndex>();
            if (!string.IsNullOrEmpty(searchDto.Term))
            {
                 query
                .Match(r => r.Field(y => y.Description).Query(searchDto.Term));
            }
            if (!string.IsNullOrEmpty(searchDto.CategoryId))
            {
                query
               .Match(r => r.Field(y => y.CategoryId).Query(searchDto.CategoryId));
            }
            if (searchDto.Stars.HasValue)
            {
                query
               .Match(r => r.Field(y => y.Stars).Query(searchDto.Stars.Value.ToString()));
            }
            if (searchDto.PriceFrom.HasValue)
            {
                query
               .Range(r => r.NumberRange(z => z.Field(t => t.Price).From(searchDto.PriceFrom)
               .To(searchDto.PriceTo)));
            }
            var result = await _searchOperation.SearchIndexElastic<ProductIndex>
                (query);
            return result;
        }

I debugged the query and it always just sending one parameter although other conditions are still active. Any other ways?

Hi @amrswalha

I don't work with .Net but I believe you need to use bool query to have more than one clause.

Hello @RabBit_BR
Thanks for the suggestion, tried that and still it sends one query parameter

public async Task<List<ProductIndex>> Search(ProductSearchDto searchDto)
        {
            Action<QueryDescriptor<ProductIndex>> query = null;
            if (!string.IsNullOrEmpty(searchDto.Term))
            {
                query = q => q
                .Bool(t => t.Must(z => z.Match(r => r.Field(y => y.Description).Query(searchDto.Term))));
            }
            if (!string.IsNullOrEmpty(searchDto.CategoryId))
            {
                query = q => q
                 .Bool(t => t.Must(z => z.Match(r => r.Field(y => y.CategoryId).Query(searchDto.CategoryId))));
            }
            if (searchDto.Stars.HasValue)
            {
                query = q => q
                .Bool(t => t.Must(z => z.Match(r => r.Field(y => y.Stars).Query(searchDto.Stars.Value.ToString()))));
            }
            if (searchDto.PriceFrom.HasValue)
            {
                query = q => q
                .Bool(t => t.Must(z => z.Range(r => r.NumberRange(z => z.Field(t => t.Price).From(searchDto.PriceFrom)
               .To(searchDto.PriceTo)))));
            }
            var result = await _searchOperation.SearchIndexElastic<ProductIndex>
                (query);
            return result;
        }

try this:

public async Task<List<ProductIndex>> Search(ProductSearchDto searchDto)
{
    Func<QueryContainerDescriptor<ProductIndex>, QueryContainer> query = null;

    if (!string.IsNullOrEmpty(searchDto.Term) || !string.IsNullOrEmpty(searchDto.CategoryId) || searchDto.Stars.HasValue || searchDto.PriceFrom.HasValue || searchDto.PriceTo.HasValue)
    {
        query = q => q.Bool(b =>
        {
            var mustClauses = new List<Func<QueryContainerDescriptor<ProductIndex>, QueryContainer>>();

            if (!string.IsNullOrEmpty(searchDto.Term))
            {
                mustClauses.Add(z => z.Match(r => r.Field(y => y.Description).Query(searchDto.Term)));
            }

            if (!string.IsNullOrEmpty(searchDto.CategoryId))
            {
                mustClauses.Add(z => z.Match(r => r.Field(y => y.CategoryId).Query(searchDto.CategoryId)));
            }

            if (searchDto.Stars.HasValue)
            {
                mustClauses.Add(z => z.Match(r => r.Field(y => y.Stars).Query(searchDto.Stars.Value.ToString())));
            }

            if (searchDto.PriceFrom.HasValue || searchDto.PriceTo.HasValue)
            {
                mustClauses.Add(z => z.Range(r => r.Field(y => y.Price).GreaterThanOrEquals(searchDto.PriceFrom).LessThanOrEquals(searchDto.PriceTo)));
            }

            return b.Must(mustClauses.ToArray());
        });
    }

    var result = await _searchOperation.SearchIndexElastic<ProductIndex>(q => q.Query(query));
    return result;
}

@RabBit_BR
Thanks for the support, but still no luck. This code is for an older version of the SDK.

Try using the must or should clause like this example. I don't know how to adapt to this client but it would probably be the way to the solution.

I am having the same issue with the newer 8.x client. have you managed to solve it?

@catsupplanetarrabbit
Yes, I managed finally to do it, see below code.

I created an object from "SearchRequest" class and filled the "Query" property with && operator with other conditions:

SearchRequest searchRequest = new SearchRequest();
if (!string.IsNullOrEmpty(searchDto.CategoryId))
{
    searchRequest.Query = searchRequest.Query &&
    new MatchQuery(new Field("categoryId"))
    {
        Query = searchDto.CategoryId
    };
}
if (searchDto.PriceFrom.HasValue)
{
    searchRequest.Query = searchRequest.Query
    && new NumberRangeQuery(new Field("price"))
    {
        From = searchDto.PriceFrom,
        To = searchDto.PriceTo
    };
}
if (searchDto.Stars.HasValue)
{
    searchRequest.Query = searchRequest.Query &&
    new MatchQuery(new Field("stars"))
    {
        Query = searchDto.Stars.ToString()
    };
}
if (!string.IsNullOrEmpty(searchDto.Term))
{
    searchRequest.Query = searchRequest.Query && (
        new QueryStringQuery()
        {
            DefaultField = new Field("description"),
            Query = $"*{searchDto.Term}*"
        }
    ||
        new QueryStringQuery()
        {
            DefaultField = new Field("title"),
            Query = $"*{searchDto.Term}*"
        }
    );

}

Hope it helps

1 Like