Help constructing .NET NEST query


#1

Hi there,

I'm using the NEST .NET client (6.3.1), and trying to compose a search query that is based on a number of (optional) parameters.

Here's what i've got so far:

var searchResponse = await _client.SearchAsync<Listing>(s => s
                .Query(qq =>
                {
                    var filters = new List<QueryContainer>();

                    if (filter.CategoryType.HasValue)
                    {
                        filters.Add(qq.Term(p => p.CategoryType, filter.CategoryType.Value));
                    }

                    if (filter.StatusType.HasValue)
                    {
                        filters.Add(qq.Term(p => p.StatusType, filter.StatusType.Value));
                    }

                    if (!string.IsNullOrWhiteSpace(filter.Suburb))
                    {
                        filters.Add(qq.Term(p => p.Suburb, filter.Suburb));
                    }

                    return ?????; // what do i do her?
                })
            );

filter is an object with a bunch of nullable properties. So, whatever has a value i want to add as a match query.

So, to achieve that i'm trying to build up a list of QueryContainer's (not sure that's the right way), but struggling to figure out how to return that as a list of AND predicates.

Any ideas?

Thanks


(Russ Cam) #2

What you're essentially trying to do is combine multiple queries together, where each query would run in a filter clause. The bool query is a compound query that can be used for this purpose, and the queries can be added as filter clauses.

Take a look at the documentation on writing bool queries with NEST, as NEST overloads operators for query types to allow them to be easily combined with &&, ||, and to run in a filter context with + unary operator.

Another feature of NEST that helps out here is the notion of conditionless queries; if the input to a query is determined to be conditionless i.e. a null or empty string for example, then NEST will omit the query altogether from the request. This means that you don't need to check whether each filter property is null before adding to the set of filters, NEST will perform this filtering by default.


#3

Hey Russ, thanks for your reply.

Great tips. I almost got it working. Here's my code:

var searchResponse = _client.Search<Listing>(s => s
                .Query(q => q
                    .Bool(b => b
                        .Must(
                            bs => bs.Terms(p => p.Field(f => f.ListingId)
                                .Terms(filter.ListingIds)),
                            bs => bs.Term(p => p.Category, filter.Category),
                            bs => bs.Term(p => p.Status, filter.Status),
                            bs => bs.Term(p => p.Suburb, filter.Suburb),
                            bs => bs.DateRange(p => p.Field(f => f.SoldOrLeasedOn)
                                .GreaterThanOrEquals(filter.SoldOrLeasedAfter)),
                            bs => bs.Range(p => p.Field(f => f.Price)
                                .GreaterThanOrEquals(filter.MinPrice)
                                .LessThanOrEquals(filter.MaxPrice)),
                            bs => bs.GeoBoundingBox(p => p.Field(f => f.LatLong)
                                .BoundingBox(filter.BoundingBox.TopLeft.Lat, filter.BoundingBox.TopLeft.Long,
                                            filter.BoundingBox.BottomRight.Lat, filter.BoundingBox.BottomRight.Long))
                        )
                    )
                )
            );

So it worked, with the exception of the .GeoBoundingBox part. filter.BoundingBox is a sub-class, so that falls over if it's null. So i'd need to check for the existence here. As soon as you introduce a conditional aspect here, can't use the Fluent DSL, right?


(Russ Cam) #4

Please could you format your code, using </> icon as explained in this guide. It will make your post more readable!

You can also use markdown style like:

```
CODE
```

This is the icon to use if you are not using markdown format:


#5

Sorry. :blush:

Updated.


(Russ Cam) #6

You can convert the expression to a method group, and return null if the condition isn't met. For example,

var searchResponse = _client.Search<Listing>(s => s
                .Query(q => q
                    .Bool(b => b
                        .Must(
                            bs => bs.Terms(p => p.Field(f => f.ListingId)
                                .Terms(filter.ListingIds)),
                            bs => bs.Term(p => p.Category, filter.Category),
                            bs => bs.Term(p => p.Status, filter.Status),
                            bs => bs.Term(p => p.Suburb, filter.Suburb),
                            bs => bs.DateRange(p => p.Field(f => f.SoldOrLeasedOn)
                                .GreaterThanOrEquals(filter.SoldOrLeasedAfter)),
                            bs => bs.Range(p => p.Field(f => f.Price)
                                .GreaterThanOrEquals(filter.MinPrice)
                                .LessThanOrEquals(filter.MaxPrice)),
                            bs =>
                            {
                                // if bounding box is null, don't filter on it
                                if (filter.BoundingBox == null)
                                {
                                    return null;
                                }
                                
                                return bs.GeoBoundingBox(p => p
                                    .Field(f => f.LatLong)
                                    .BoundingBox(
                                        filter.BoundingBox.TopLeft.Lat, 
                                        filter.BoundingBox.TopLeft.Long,
                                        filter.BoundingBox.BottomRight.Lat, 
                                        filter.BoundingBox.BottomRight.Long)
                                    );
                            }
                    )
                )
            );

You can also refactor it out into a separate method that accepts a QueryContainerDescriptor<Listing> and returns a QueryContainer.


#7

Ah perfect, of course! Thanks Russ :slight_smile:


(system) #8

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.