Search code examples
c#asp.netelasticsearch.net-corenest

Dynamic Elastic search query in c# NEST


Started working on NEST api for elastic search recently, got stuck on following query, the data.e would be dynamically populated using the input from client in the HttpGet, ex: user sends eventA,eventB,eventC then we would add in the should part:

GET events/_search
{
  "_source": false, 
  "query": {
    "bool": {
      "must": [
        {"range": {
          "timestamp": {
            "gte": 1604684158527,
            "lte": 1604684958731
            }
        }},
        {"nested": {
          "path": "data",
          "query": {
            "bool": {
              "should": [
                {"match": {
                   "data.e": "eventA"
                }},
                {"match": {
                  "data.e": "eventB"
                }},
                {"match": {
                   "data.e": "eventC"
                }},
              ]
            }
            },
          "inner_hits": {} 
        }}
      ]
    }
  }
}

Following is what I came up with till now:

var graphDataSearch = _esClient.Search<Events>(s => s
                .Source(src => src
                    .Includes(i => i
                        .Field("timestamp")
                        )
                 )
                .Query(q => q
                    .Bool(b => b
                        .Must(m => m
                                .Range(r => r
                                    .Field("timestamp")
                                    .GreaterThanOrEquals(startTime)
                                    .LessThanOrEquals(stopTime)
                                    ),
                                m => m
                                .Nested(n => n
                                    .Path("data")
                                    .Query(q => q
                                        .Bool(bo => bo
                                            .Should(
                                            // what to add here?
                                    )
                                    )
                                )
                        )
                    )
                ));

Can someone please help how to build the should part dynamically based on what input the user sends?

Thanks.


Solution

  • You can replace the nested query in the above snippet as shown below

    // You may modify the parameters of this method as per your needs to reflect user input
    // Field can be hardcoded as shown here or can be fetched from Event type as below
    // m.Field(f => f.Data.e)
    
    public static QueryContainer Blah(params string[] param)
    {
        return new QueryContainerDescriptor<Events>().Bool(
            b => b.Should(
                s => s.Match(m => m.Field("field1").Query(param[0])),
                s => s.Match(m => m.Field("field2").Query(param[1])),
                s => s.Match(m => m.Field("field3").Query(param[2]))));
    }
    

    What we are essentially doing here is we are returning a QueryContainer object that will be passed to the nested query

    .Query(q => Blah(<your parameters>))
    

    The same can be done by adding this inline without a separate method. You may choose which ever route you perfer. However, in general, having a method of its own increases the readability and keeps things cleaner.

    You can read more about Match usage here

    Edit:

    Since you want to dynamically add the match queries inside this, below is a way you can do it.

    private static QueryContainer[] InnerBlah(string field, string[] param)
    {
        QueryContainer orQuery = null;
        List<QueryContainer> queryContainerList = new List<QueryContainer>();
        foreach (var item in param)
        {
            orQuery = new MatchQuery() {Field = field, Query = item};
            queryContainerList.Add(orQuery);
        }
        return queryContainerList.ToArray();
    }
    

    Now, call this method from inside of the above method as shown below

    public static QueryContainer Blah(params string[] param)
    {
        return new QueryContainerDescriptor<Events>().Bool(
            b => b.Should(
                InnerBlah("field", param)));
    }