Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"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")
                );
like image 379
Roger Joys Avatar asked Nov 17 '12 19:11

Roger Joys


2 Answers

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>().

like image 195
Martijn Laarman Avatar answered Sep 30 '22 19:09

Martijn Laarman


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))
                );
like image 45
jhilden Avatar answered Sep 30 '22 17:09

jhilden