Roger Joys
Roger Joys

Reputation: 338

"Dynamically" creating a filter in NEST

I have an interesting challenge, which I think there is an easy answer to.

I know that NEST filters work correctly when syntactically you do something like this:

var andFilter = FilterFactory.AndFilter(
                    FilterFactory.TermFilter("name.first", "shay1"),
                    FilterFactory.TermFilter("name.first", "shay4")
                );

My base services should allow an the caller to pass in some sort of enumerable list of items to filter.

I'd basically like to be able programmatically achieve something like this (filters is passed into the method):

var andFilter = new FilterDescriptor();
foreach (var filter in filters) 
{
     andFilter = filter concatenated to andFilter
}

In other words if I passed in an array of { {"first.name", "joe"}, {"first.name", "jim"}, {"first.name", "frank"}} I would like to produce the equivalent of

var andFilter = FilterFactory.AndFilter(
                    FilterFactory.TermFilter("name.first", "joe"), 
                    FilterFactory.TermFilter("name.first", "joe"),
                    FilterFactory.TermFilter("name.first", "frank")
                );

Upvotes: 6

Views: 5953

Answers (3)

jhilden
jhilden

Reputation: 12439

Martijn's answer is the best but I thought I would add an example that I created that is working for me, hopefully it will be helpful to others. I built a list of BaseQuery objects and then put that in my query using the .ToArray() method.

    #region build query

    var query = new List<BaseQuery>
                {
                    Query<IAuthForReporting>.Range(r => r.OnField(f => f.AuthResult.AuthEventDate)
                                                    .From(authsByDateInput.StartDate.ToEPCISFormat())
                                                    .To(authsByDateInput.EndDate.ToEPCISFormat()))
                };
    if (authsByDateInput.AuthResult != AuthResultEnum.SuccessOrFailure)
    {
        var success = authsByDateInput.AuthResult == AuthResultEnum.Success;
        query.Add(Query<IAuthForReporting>.Term(t => t.AuthResult.AuthenticationSuccessful, success));
    }
    if (authsByDateInput.ProductID != null)
    {
        query.Add(Query<IAuthForReporting>.Term(t => t.AuthResult.ProductID, authsByDateInput.ProductID.Value));
    }

    if (!authsByDateInput.CountryIDs.IsNullOrEmpty())
    {
        query.Add(Query<IAuthForReporting>.Terms(t => t.AuthResult.Address.CountryID, authsByDateInput.CountryIDs.Select(x=> x.Value.ToString()).ToArray()));
    }
    #endregion

        var result =
            ElasticClient.Search<IAuthForReporting>(s =>
                                                    s.Index(IndexName)
                                                     .Type(TypeName)
                                                     .Size(0)
                                                     .Query(q =>
                                                            q.Bool(b =>
                                                                   b.Must(query.ToArray())
                                                                )
                                                        )
                                                     .FacetDateHistogram(t => t.OnField(f => f.AuthResult.AuthEventDate).Interval(DateInterval.Day))
                );

Upvotes: 1

Martijn Laarman
Martijn Laarman

Reputation: 13536

Using the lambda based DSL you can do the following:

var termsFilters = from tp in termParameters
                   let field = ToCamelCaseNestedNames(tp.SearchField)
                   let terms = tp.SearchValues
                   select Filter.Terms(field, terms);

var prefixFilters = from tp in prefixParameters
                    let field = ToCamelCaseNestedNames(tp.SearchField)
                    let prefix = tp.SearchValues.FirstOrDefault().ToLowerInvariant()
                    select Filter.Prefix(field, prefix);

var search = client.Search(s => s
    .From(0)
    .Size(20)
    .Filter(f => f.And(termsFilters.Concat(prefixFilters).ToArray()))
);

Which i think reads a bit better :)

Nest now also supports conditionless queries so if any tp.SearchValues is null, empty or all empty strings or tp.SearchField is null or empty it will skip that terms/prefix query.

You can revert this behavior easily though:

var search = client.Search(s => s
    .Strict()
    .From(0)
    .Size(20)
    .Filter(f => f.And(termsFilters.Concat(prefixFilters).ToArray()))
);

which will throw a DslException if an empty query is generated.

As a last note client.Search() will return a QueryResult<dynamic> if you can strongly type your documents so can do a client.Search<MyDocument>().

Upvotes: 9

Roger Joys
Roger Joys

Reputation: 338

I was able to solve this after some R&D on the subject with something akin to the following. I'll need to do some additional work on the And and Or support:

        IList<IFilterBuilder> conditions = new List<IFilterBuilder>();
        if (termParameters != null)
            foreach (var termParameter in termParameters)
                conditions.Add(FilterFactory.TermsFilter(ToCamelCaseNestedNames(termParameter.SearchField), termParameter.SearchValues));

        if (prefixParameters != null)
            foreach (var prefixParameter in prefixParameters)
                conditions.Add(FilterFactory.PrefixFilter(ToCamelCaseNestedNames(prefixParameter.SearchField), prefixParameter.SearchValues.First().ToLowerInvariant()));

        var filters = FilterFactory.AndFilter();
        filters.Add(FilterFactory.AndFilter(conditions.ToArray()));

        MatchAllQueryBuilder matchAllQueryBuilder = new MatchAllQueryBuilder();
        FilteredQueryBuilder filteredQueryBuilder = new FilteredQueryBuilder(matchAllQueryBuilder, filters);
        SearchBuilder searchBuilder = new SearchBuilder();
        searchBuilder.Query(filteredQueryBuilder);
        searchBuilder.Size(maxResults);

Upvotes: -1

Related Questions