Static Query Building with NEST
Asked Answered
T

2

12

I'm playing around with Elasticsearch and NEST.

I do have some trouble understanding the various classes and interfaces which can be used to create and build static queries.

Here's a simplified example of what I want to achieve:

using Nest;
using System;
using System.Text;

namespace NestTest
{
    public class Product
    {
        public string Name { get; set; }
        public int Price { get; set; }
    }

    public class ProductFilter
    {
        public string[] IncludeNames { get; set; }
        public string[] ExcludeNames { get; set; }
        public int MaxPrice { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var filter = new ProductFilter();
            filter.MaxPrice = 100;
            filter.IncludeNames = new[] { "Notebook", "Workstation" };
            filter.ExcludeNames = new[] { "Router", "Modem" };

            var query = CreateQueryFromFilter(filter);

            var client = new ElasticClient();

            // Test Serialization
            var serialized = Encoding.UTF8.GetString(client.Serializer.Serialize(query));
            Console.WriteLine(serialized);

            // TODO: How to convert the IQuery to QueryContainer?
            //client.Search<Product>(s => s.Query(q => query));
        }

        private static IQuery CreateQueryFromFilter(ProductFilter filter)
        {
            var baseBoolean = new BoolQueryDescriptor<Product>();

            if (filter.IncludeNames != null && filter.IncludeNames.Length > 0)
            {
                foreach (var include in filter.IncludeNames)
                {
                    // TODO: This overwrites the previous must
                    baseBoolean.Must(q => q.Term(t => t.Name, include));
                }
            }

            if (filter.ExcludeNames != null && filter.ExcludeNames.Length > 0)
            {
                foreach (var exclude in filter.ExcludeNames)
                {
                    // TODO: This overwrites the previous must
                    baseBoolean.MustNot(q => q.Term(t => t.Name, exclude));
                }
            }

            if (filter.MaxPrice > 0)
            {
                // TODO: This overwrites the previous must
                baseBoolean.Must(q => q.Range(r => r.LowerOrEquals(filter.MaxPrice).OnField(f => f.Price)));
            }

            return baseBoolean;
        }
    }
}

As you can see, I'd like to create some kind of query object (most likely BoolQuery) and then fill this object later on. I've added some TODOS in code where I have the actual problems. But in general, there are just too many possibilities (IQuery, QueryContainer, XXXQueryDescriptor, SearchDescriptor, SearchRequest) and I cannot figure out how to successfully "build" a query part by part.

Anybody who could enlighten me?

Taveras answered 8/8, 2014 at 21:1 Comment(0)
F
23

Combinding boolean queries is described in the documentation here:

http://nest.azurewebsites.net/nest/writing-queries.html

That page is slightly outdated and will be updated soon although most of it still applies I updated your CreateQueryFromFilter method to showcase the several ways you can formulate queries:

private static IQueryContainer CreateQueryFromFilter(ProductFilter filter)
{
    QueryContainer queryContainer = null;

    if (filter.IncludeNames != null && filter.IncludeNames.Length > 0)
    {
        foreach (var include in filter.IncludeNames)
        {
            //using object initializer syntax
            queryContainer &= new TermQuery()
            {
                Field = Property.Path<Product>(p => p.Name),
                Value = include
            };
        }
    }

    if (filter.ExcludeNames != null && filter.ExcludeNames.Length > 0)
    {
        foreach (var exclude in filter.ExcludeNames)
        {
            //using static Query<T> to dispatch fluent syntax
            //note the ! support here to introduce a must_not clause
            queryContainer &= !Query<Product>.Term(p => p.Name, exclude);
        }
    }

    if (filter.MaxPrice > 0)
    {
        //fluent syntax through manually newing a descriptor
        queryContainer &= new QueryDescriptor<Product>()
            .Range(r => r.LowerOrEquals(filter.MaxPrice).OnField(f => f.Price)
        );
    }

    return queryContainer;
}

Here's how you can pass that to a search operation:

static void Main(string[] args)
{
    //using the object initializer syntax
    client.Search<Product>(new SearchRequest()
    {
        Query = query
    });

    //using fluent syntax
    client.Search<Product>(s => s.Query(query));
}
Fou answered 11/8, 2014 at 13:11 Comment(4)
Thank you! that is good so far. Is there also a way to add "should" parts to the query or do I need to save all "shoulds" in my own array and add them at the end?Taveras
Shoulds can be added using |= put beware that boolean queries do not quite follow algebraic boolean logic! as explained here https://mcmap.net/q/1007847/-how-do-amp-amp-and-work-constructing-queries-in-nest.Fou
One "gotcha" I ran into is the QueryContainer declaration. I had QueryContainer query = new QueryContainer();, rather than setting it to null. Then used |= new TermQuery { } to combine terms. This resulted in an empty QueryContainer and the API call return "bad request".Jillian
I can't fint Property.Path<T> in Nest 2.3.1. Anyone got a clue?Forgetmenot
A
0

NEST has many updates, now you can use Infer to access Field (checked with NEST 7.17.5):

var query = new MatchQuery
{
   Query = "name of product",
   Field = Infer.Field<Product>(p => p.Name),
};

PS: Now library renamed to: Elastic.Clients.Elasticsearch.

Address answered 4/5, 2023 at 8:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.