2
votes

My ASP.NET Web API has a Search method to search for a specific query in the Elasticsearch database. Also the user can set sort parameters like the property to sort by or if it should be ordered ascending or descending.

http://localhost/api/search?query=jon&sortBy=lastname&sortOrder=desc

The controller passes the request to Elasticsearch using NEST.

var sortProperty = typeof(T).GetProperty(options.SortBy, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);

var sortOrder = options.SortOrder.IndexOf("desc", System.StringComparison.OrdinalIgnoreCase) >= 0
            ? SortOrder.Descending
            : SortOrder.Ascending;

var result = await this.elasticClient.SearchAsync<Person>(search => search
            .Query(q => q.MultiMatch(m => m
                    .Query(query)))
            .Sort(sort => sort.Field(sfd => sfd
                .Field(new Field(sortProperty))
                .Order(sortOrder)));

The sortProperty can be a text field, like firstname and lastname in this sample. To be able to sort by this text fields, I've added the "raw" keyword fields.

{
    "people": {
        "mappings": {
            "person": {
                "properties": {
                    "birthdate": {
                        "type": "date"
                    },
                    "firstname": {
                        "type": "text",
                        "fields": {
                            "raw": {
                                "type": "keyword"
                            }
                        }
                    },
                    "id": {
                        "type": "integer"
                    },
                    "lastname": {
                        "type": "text",
                        "fields": {
                            "raw": {
                                "type": "keyword"
                            }
                        }
                    }
                }
            }
        }
    }
}

Now I have to add the suffix "raw" to the properties firstname and lastname.

.Sort(sort => sort.Descending(p => p.Firstname.Suffix("raw")));

But how do I add this to the more generic version I used above, where sortProperty and sortOrder are used to create the SortFieldDescriptor?

Something like the following does not work:

.Sort(sort => sort.Field(sfd => sfd
    .Field(new Field(sortProperty).Suffix("raw"))
    .Order(sortOrder)));
1

1 Answers

1
votes

.Suffix() is intended to be used with member expressions only.

I think you can make this easier by simply using a string to represent the field.

So, instead of

var sortProperty = typeof(T).GetProperty(options.SortBy, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);

and using PropertyInfo to create an instance of Field, you could use

var sortOrder = options.SortOrder.IndexOf("desc", System.StringComparison.OrdinalIgnoreCase) >= 0
            ? SortOrder.Descending
            : SortOrder.Ascending;

var result = await this.elasticClient.SearchAsync<Person>(search => search
            .Query(q => q
                .MultiMatch(m => m
                    .Query(query)
                )
            )
            .Sort(sort => sort
                .Field(sfd => sfd
                    .Field($"{options.SortBy}.raw")
                    .Order(sortOrder)
                )
            );

Note that when instantiating a Field from a string, the string value is taken verbatim, so it needs to match the casing of the field in Elasticsearch. In contrast, expressions and PropertyInfo are transformed according to .DefaultFieldNameInferrer() on ConnectionSettings, which will camel case by default.

There are implicit conversions from string, PropertyInfo and Expression<Func<T, object>> to an instance of Field. Take a look at the Field inference documentation.