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