elastic/elasticsearch-net

Query on multiple fields using "fluent" syntax generates broken DSL.

Closed this issue · 2 comments

Elastic.Clients.Elasticsearch version: 8.19.7

Elasticsearch version: 8.16.1

.NET runtime version: 8.0.20

Operating system version: Windows 11 Enterprise 23H2

Description of the problem including expected versus actual behavior:

In NEST it was possible to create fluent queries on multiple fields like this:
.Fields(f => f.Field(ff => ff.Name).Field(ff => ff.Path))

That syntax is no longer available. When I try to do something similar I get an unexpected behaviour.

var query = new QueryDescriptor<Document>().QueryString(qs => qs
    .Fields(f => new[] { f.Name, f.Path })
    .Query("title"));

var request = new SearchRequestDescriptor<Document>().Query(query);
request.Indices(PrependPrefix(typeof(Document).ElasticsearchIndexName()));
var response = Client.Search<Document>(request);

Generated DSL:

{
  "query": {
    "query_string": {
      "fields": [
        "path.name"
      ],
      "query": "title"
    }
  }
}

Expected behavior

I'm expecting the fields to be an array instead of concatenated to a single string with a dot separator.

{
  "query": {
    "query_string": {
      "fields": [
        "name",
        "path"
      ],
      "query": "title"
    }
  }
}

Workarounds

This generates a proper query:

var query = new QueryDescriptor<Document>().QueryString(qs => qs
	.Fields("name, path")
	.Query("title"));

This more fluent syntax also seems to work:

var fields = Fields.FromFields(
[
    Infer.Field<Document>(i => i.Name),
    Infer.Field<Document>(i => i.Path)
]);

var query = new QueryDescriptor<Document>().QueryString(qs => qs
    .Fields(fields)
    .Query("title"));

Hi @sictransit ,

In NEST it was possible to create fluent queries on multiple fields like this:
.Fields(f => f.Field(ff => ff.Name).Field(ff => ff.Path))

That syntax is no longer available.

That's correct, this syntax is (currently) no longer available. It got replaced with this syntax (params overload):

.Fields(f => f.Name, f => f.Path)

When I try to do something similar I get an unexpected behaviour.

In the FieldExpressionVisitor that converts the field expressions to a string, we visit all MemberExpressions and concatenate them with a .. This behavior is introduced for cases like f => f.Main.Sub.

We currently do not explicitly validate that the input LambdaExpression.Body is a MemberExpression itself, which basically allows to pass arbitrary constructs:

.Fields(f => new
{
    f.FirstName,
    f.LastName,
    X = new
    {
        Y = f.Age
    }
})

// {"query_string":{"fields":["age.lastName.firstName"],"query":"title"}}

This is not really intended, but I'm hesitant to change the behavior at this point, since it may break existing code.

Workarounds

These are all perfectly valid as well, but intended for cases where the descriptor is either non-generic or when the object initializer syntax is used.

The most straightforward syntax to be used in generic descriptors is the one I described in the beginning.

Please let me know if that solved your issue.

The proposed syntax works fine. I'm happy!

I tried lots of variants yesterday, but apparently not the one you suggested. On the other hand my IDE is not very helpful either:

Image

NEST had accumulated quite a lot of docs and code examples over the years, so you could always find something similar to what you were trying to do. Copilot is mostly hallucinating when it comes the new version of the client, so not helpful at all.