elasticsearch-net icon indicating copy to clipboard operation
elasticsearch-net copied to clipboard

Add a "multiple query descriptors" example to basic docs

Open seanwm opened this issue 1 year ago • 2 comments

Is your feature request related to a problem? Please describe. I need to write queries like this:

{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "propOne": "my_value_1"
          }
        },
        {
          "term":
          {
            "propTwo": "my_value_2"
          }
        }
      ]
    }
  }
}

I couldn't find any examples of that in the current documentation. Flailing around in VS Code I can end up with compilable code like this:

Query(q =>
                q.Bool(b => b
                    .Filter(f => f.Term(t => t.Field(f => f.PropOne).Value("my_value_1")))
                    .Filter(f => f.Term(t => t.Field(f => f.PropTwo).Value("my_value_2))))
                ));

But that doesn't work; only the "PropTwo" filter ends up in the resulting query. One answer I found via another tangentially-related issue here was to do something like this:

var filters = new List<Action<QueryDescriptor<MyType>>>();
filters.Add(f => f.Term(t => t.Field(f => f.PropOne).Value("my_value_1")));
filters.Add(f => f.Term(t => t.Field(f => f.PropTwo).Value("my_value_2")));

...and then include them in my query with .Filter(filters.ToArray()). If there's a way to do what I want simply by chaining, I'm not sure what it might be.

I am no doubt bad at intuiting fluent interfaces, but it felt frustrating to know what output I wanted but be unable to create that in code.

Describe the solution you'd like For me, it would have helped a lot to have one or two examples on a page like "Query examples" demonstrate how to have more than one query descriptor under something like "Filter()".

Additional context I appreciate the library!

seanwm avatar Nov 07 '24 19:11 seanwm

Hi @seanwm ,

providing an example for BoolQuery in the documentation is an excellent suggestion 🙂 I'll take care about adding this!

In the meantime:

await client.SearchAsync<Person>(s => s
	.Query(q =>
		q.Bool(b => b
			.Filter([ // <- use the "params" overload
				f => f.Term(t => t.Field(f => f.FirstName).Value("my_value_1")),
				f => f.Term(t => t.Field(f => f.Email).Value("my_value_2"))
			])
		)
	)
);

Your code was very close. The params overload is a little bit tricky to use with the descriptor actions. Calling .Filter() multiple times just overrides the previous value - like you already noticed. This is always the case unless the fluent API method starts with something like Add..., Remove.... In these cases you can expect the method to alter the state accordingly, in all other situations you should assume the previous value to get overwritten.

flobernd avatar Nov 08 '24 08:11 flobernd

Ah, this is what I spent 3 hours looking for. Much, appreciate it.

I need to add filters conditionally. So I preferred this notation:

var searchResponse = await client.SearchAsync<EsProductDocModel>(s => s
    .Index(_productIndexName)
    .From((pageOffset) * size)
    .Size(size)
    .Query(qd => qd
        .Bool(
            brd =>
            {
                // Filters
                List<Action<QueryDescriptor<EsProductDocModel>>> filters = new();
                if (categoryId is not null)
                    filters.Add(f =>f.Term(t => t.Field(f => f.Category.Id).Value(categoryId))
                );
                filters.Add(f => f.Range(r => r.NumberRange(n => n.Field(f => f.OverallCalculatedRating).Gte(minCalRating))));
                filters.Add(f => f.Range(r => r.NumberRange(n => n.Field(f => f.OverallCalculatedRating).Lte(maxCalRating))));
                brd.Filter(filters.ToArray());
            }
        )
    );
    return searchResponse;
}

kzlsahin avatar Feb 28 '25 06:02 kzlsahin