Range query on BigDecimal in Hibernate Search - hibernate-search

I am trying to do a range query on an entity field of type BigDecimal but couldn't make it work. This is what I've done so far.
This is the entity class.
public class Deal {
#Field(store = Store.YES)
#Field(name = "budget_Sort", store = Store.YES, normalizer= #Normalizer(definition = SearchConstants.LOWER_CASE_NORMALIZER))
#FieldBridge(impl = BigDecimalNumericFieldBridge.class)
#SortableField(forField = "budget_Sort")
#Column(name = "BUDGET", precision = 10, scale = 2)
private BigDecimal budget = new BigDecimal(0);
//other fields and methods omitted for brevity
}
The custom FieldBridge for BigDecimal is as follows. I chose type DOUBLE as the converted type.
public class BigDecimalNumericFieldBridge implements MetadataProvidingFieldBridge, TwoWayFieldBridge {
private static final BigDecimal storeFactor = BigDecimal.valueOf( 100 );
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
if ( value != null ) {
BigDecimal decimalValue = (BigDecimal) value;
Double indexedValue = decimalValue.multiply( storeFactor ).doubleValue();
luceneOptions.addNumericFieldToDocument( name, indexedValue, document );
luceneOptions.addNumericDocValuesFieldToDocument(name, indexedValue, document);
}
}
#Override
public Object get(String name, Document document) {
String fromLucene = document.get( name );
if (Objects.nonNull(fromLucene)) {
BigDecimal storedBigDecimal = new BigDecimal(fromLucene);
return storedBigDecimal.divide(storeFactor);
} else {
return null;
}
}
#Override
public String objectToString(Object object) {
return object.toString();
}
#Override
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
builder.field( name, FieldType.DOUBLE );
}
}
I then defined a class Range to pass lower bound, upper bound and field data type.
public class Range {
private String lowerBound;
private String upperBound;
private String dataType;
}
And created the following object to test this range query.
Range budgetRange = new Range("9500", "10500",SearchConstants.BIGDECIMAL);
The actual query building logic is as follows.
protected Query rangeFilterBuilder(String key, String field) {
Query rangeQuery = null;
String lowerBound = searchRequest.getRangeFilters().get(key).getLowerBound();
String upperBound = searchRequest.getRangeFilters().get(key).getUpperBound();
String dataType = searchRequest.getRangeFilters().get(key).getDataType();
switch (dataType) {
case SearchConstants.INTEGER:
rangeQuery = queryBuilder.range().onField(field).from(Integer.valueOf(lowerBound)).to(Integer.valueOf(upperBound)).createQuery();
break;
case SearchConstants.LONG:
rangeQuery = queryBuilder.range().onField(field).from(Long.valueOf(lowerBound)).to(Long.valueOf(upperBound)).createQuery();
break;
case SearchConstants.DOUBLE:
rangeQuery = queryBuilder.range().onField(field).from(Double.valueOf(lowerBound)).to(Double.valueOf(upperBound)).createQuery();
break;
case SearchConstants.STRING:
rangeQuery = queryBuilder.range().onField(field).from((lowerBound)).to(upperBound).createQuery();
break;
case SearchConstants.DATE:
rangeQuery = queryBuilder.range().onField(field).from(LocalDateTime.parse(lowerBound)).to(LocalDateTime.parse(upperBound)).createQuery();
break;
case SearchConstants.BIGDECIMAL:
rangeQuery = queryBuilder.range().onField(field).from(Double.valueOf(lowerBound)).to(Double.valueOf(upperBound)).createQuery();
}
return rangeQuery;
}```
This always return 0 matching result regardless of the range. I've tried other fields of type int or long and they work as expected. Is there something that I missed for BigDecimal range query?

The problem is that you're multiplying the value by 100 when indexing, but not when querying. So when you search for value 42, the range [40, 45] won't match, but the range [4000, 4500] will.
The solution would be to multiply the bounds by 100 in your function, for the "BigDecimal" case:
switch (dataType) {
case SearchConstants.INTEGER:
rangeQuery = queryBuilder.range().onField(field).from(Integer.valueOf(lowerBound)).to(Integer.valueOf(upperBound)).createQuery();
break;
case SearchConstants.LONG:
rangeQuery = queryBuilder.range().onField(field).from(Long.valueOf(lowerBound)).to(Long.valueOf(upperBound)).createQuery();
break;
case SearchConstants.DOUBLE:
rangeQuery = queryBuilder.range().onField(field).from(Double.valueOf(lowerBound)).to(Double.valueOf(upperBound)).createQuery();
break;
case SearchConstants.STRING:
rangeQuery = queryBuilder.range().onField(field).from((lowerBound)).to(upperBound).createQuery();
break;
case SearchConstants.DATE:
rangeQuery = queryBuilder.range().onField(field).from(LocalDateTime.parse(lowerBound)).to(LocalDateTime.parse(upperBound)).createQuery();
break;
case SearchConstants.BIGDECIMAL:
rangeQuery = queryBuilder.range().onField(field).from(Double.valueOf(lowerBound) * 100.0).to(Double.valueOf(upperBound) * 100.0).createQuery();
}
By the way, I really think your function should parse the number as a BigDecimal, not a Double, in the BigDecimal case. Then you can multiply it, and then convert it to Double, similarly to what you did in your function.
Also, you'll probably want to use Long instead of Double when indexing (and querying) a scaled BigDecimal: you'll get more performance and will be sure that the indexed number is exactly the one you want (no rounding). You don't need the range of a double for monetary amounts anyway, unless you're dealing with amounts higher than the total value of all assets on earth :)

Related

class object and property variation in dart

so i recently started learning dart and I've found something kinda interesting.
why do we use constructors and getters/setters when we can achieve same results without them? (atleast when used for basic things).
class v1{
var name;
int age;
v1(this.name, this.age);
info(){
print("my name is $name and i am $age");
}
}
class v2{
var name = "bash";
int age = 100;
info(){
print("my name is $name and i am $age");
}
}
class v3{
var namee;
int agee;
String get name => namee;
int get age => agee;
set name(String name) => this.namee = name;
set age(int age) => this.agee = age;
info(){
print("my name is $name and i am $age");
}
}
void main(){
var x = v1("bash", 100);
x.info(); //my name is bash am i am 100
var z = v2();
var Z = v2();
Z.name = "vert";
Z.age = 20;
z.info(); //my name is bash and i am 100
Z.info(); //my name is vert and i am 100
var y = v3();
y.name = "rizz";
y.age = 40;
y.info(); //my name is rizz and i am 40
}
Here's a more correct version of your class:
class User {
final bool _isMale;
String _name;
int _age;
User(this._isMale, this._name, this._age);
bool isMale => _isMale;
String get name => _name;
int get age => _age;
set name(String name) {
// Sometimes you may want to update other properties here.
// For example:
// _isUpdated = true;
_name = name;
}
set age(int age) {
_age = age;
}
void info() {
print("my name is $name and i am $age");
}
}
Constructors are useful when you want to assign initial values to the class fields. They are essential if you need to assign final fields, as they are assignable only on class initialization (see _isMale field).
Setters are useful when you want to update other fields along with the field that's being modified.
Getters protect the internal state from being modified outside. In this example, nobody can change _isMale field.
You don't need to use getters and setters unless you have to.
You use getters and setters if you need to store the data in a private field, or if you want to modify it when saving or returning the value.
class Abc {
String _field;
String _otherField;
String anotherField; // No getters and setters required for this.
String get field => _field;
set field(String field) => _field = field;
String get otherField => "The value of otherField is: " + _otherField;
set otherField(String otherField) => _otherField = "[String] " + otherField;
}
As for constructors, you use them to initialize the object with custom values. When you need to work with immutable objects (which use final variables), you'll have to use constructors to set their initial value. You can also modify the incoming value according to your need before storing it,
class Def {
final field; // Dart generates getters for this field, but it's value can't be modified once the object is instantiated.
final _otherField; // No getters for this.
Def(String field, String otherField) {
this.field = "[String] $field"
this._otherField = "[String] $otherField"
}
String describeMe() {
return "[Def]: field: $field, _otherField: $_otherField"
}
}

Cleanest way to implement multiple parameters filters in a REST API

I am currently implementing a RESTFUL API that provides endpoints to interface with a database .
I want to implement filtering in my API , but I need to provide an endpoint that can provide a way to apply filtering on a table using all the table's columns.
I've found some patterns such as :
GET /api/ressource?param1=value1,param2=value2...paramN=valueN
param1,param2...param N being my table columns and the values.
I've also found another pattern that consists of send a JSON object that represents the query .
To filter on a field, simply add that field and its value to the query :
GET /app/items
{
"items": [
{
"param1": "value1",
"param2": "value",
"param N": "value N"
}
]
}
I'm looking for the best practice to achieve this .
I'm using EF Core with ASP.NET Core for implementing this.
Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.
That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:
I.e.
{
p1: "Jake",
p2: "8"
}
The query string looks like:
.../api/customer/search?filters=XHgde0023GRw....
On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:
// this is the search filter keys, these (int) values are passed to the search client for each filter field.
public enum FilterKeys
{
None = 0,
Name,
Age,
ParentName
}
public JsonResult Search(string filters)
{
string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);
using (var context = new TestDbContext())
{
var query = context.Children.AsQueryable();
foreach (var filter in filterData)
query = filterChildren(query, filter.Key, filter.Value);
var results = query.ToList(); //example fetch.
// TODO: Get the results, package up view models, and return...
}
}
private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
{
var filterKey = parseFilterKey(key);
if (filterKey == FilterKeys.None)
return query;
switch (filterKey)
{
case FilterKeys.Name:
query = query.Where(x => x.Name == value);
break;
case FilterKeys.Age:
DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
DateTime birthDateEnd = birthDateStart.AddYears(1);
query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
break;
}
return query;
}
private FilterKeys parseFilterKey(string key)
{
FilterKeys filterKey = FilterKeys.None;
Enum.TryParse(key.Substring(1), out filterKey);
return filterKey;
}
You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.
I have invented and found it useful to combine a few filters into one type for example CommonFilters and make this type parseable from string:
[TypeConverter(typeof(CommonFiltersTypeConverter))]
public class CommonFilters
{
public PageOptions PageOptions { get; set; }
public Range<decimal> Amount { get; set; }
//... other filters
[JsonIgnore]
public bool HasAny => Amount.HasValue || PageOptions!=null;
public static bool TryParse(string str, out CommonFilters result)
{
result = new CommonFilters();
if (string.IsNullOrEmpty(str))
return false;
var parts = str.Split(new[] { ' ', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
if (part.StartsWith("amount:") && Range<decimal>.TryParse(part.Substring(7), out Range<decimal> amount))
{
result.Amount = amount;
continue;
}
if (part.StartsWith("page-options:") && PageOptions.TryParse(part.Substring(13), out PageOptions pageOptions))
{
result.PageOptions = pageOptions;
continue;
}
//etc.
}
return result.HasAny;
}
public static implicit operator CommonFilters(string str)
{
if (TryParse(str, out CommonFilters res))
return res;
return null;
}
}
public class CommonFiltersTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string str)
{
if (CommonFilters.TryParse(str, out CommonFilters obj))
{
return obj;
}
}
return base.ConvertFrom(context, culture, value);
}
}
the request looks like this:
public class GetOrdersRequest
{
[DefaultValue("page-options:50;amount:0.001-1000;min-qty:10")]
public CommonFilters Filters { get; set; }
//...other stuff
}
In this way you reduce the number of input request parameters, especially when some queries don't care about all filters
If you use swagger map this type as string:
c.MapTypeAsString<CommonFilters>();
public static void MapTypeAsString<T>(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.MapType(typeof(T), () => new OpenApiSchema(){Type = "string"});
}

Generic Func needed to perform sorting of Entity Framework collection

I have a grid with columns. When the grid column header is selected I post/ajax to server with header selected to return x rows.
In the following code, RefNo is integer while ProposalSectionNumber is a string.
How to make a generic function taking a string but return a Func to be used in the linq statement?
if (sort.dir == SortDirection.Asc)
{
switch (sort.field)
{
case "RefNo":
qry = qry.OrderBy(x => x.RefNo);
break;
case "ProposalSectionNumber":
qry = qry.OrderBy(x => x.ProposalSectionNumber);
break;
}
}
else
{
switch (sort.field)
{
case "RefNo":
qry = qry.OrderByDescending(x => x.RefNo);
break;
case "ProposalSectionNumber":
qry = qry.OrderByDescending(x => x.ProposalSectionNumber);
break;
}
}
I would like to do something like
string sortOrder = "RefNo"
var sortfunc = SortFunc(sortOrder)
if (sort.dir == SortDirection.Asc)
{
qry = qry.OrderBy(sortFunc)
}
else
{
qry = qry.OrderByDesc(sortFunc)
}
I have struggled creating the function SortFunc (which returns based on string or integer)
What is the best way to achieve this?
The problem with declaring a type for sortFunc is that it depends on the type of the field by which you sort. If all fields were of the same type, say, all strings, you could use the type of Expression<Func<MyEntity,string>> for your sortFunc variable.
There is another way of removing code duplication when sort fields do not share a common type. Introduce a generic helper method that takes sort order as a parameter, and call it instead of OrderBy/OrderByDescending:
private static IOrderedQueryable<T> AddOrderBy<T,TKey>(
IQueryable<T> orig
, Expression<Func<T,TKey>> selector
, bool isAscending
) {
return isAscending ? orig.OrderBy(selector) : orig.OrderByDescending(selector);
}
Now you can rewrite your code as follows:
var isAscending = (sort.dir == SortDirection.Asc);
switch (sort.field) {
case "RefNo":
qry = qry.AddOrderBy(x => x.RefNo, isAscending);
break;
case "ProposalSectionNumber":
qry = qry.AddOrderBy(x => x.ProposalSectionNumber, isAscending);
break;
}

How to construct a dynamic where filter in EF.Core to handle equals, LIKE, gt, lt, etc

Please how do we construct a dynamic where filter in EF.Core to handle:
Query.Where(fieldName, compareMode, value)
I basically Expect to use it like below:
[HttpGet(Name = nameof(GetStaff))]
public IActionResult GetStaffAsync([FromQuery] QueryParams p)
{
var s = db.Staff.AsNoTracking()
.Where(p.filter_field, p.filter_mode, p.filter_value)
.OrderByMember(p.sortBy, p.descending);
var l = new Pager<Staff>(s, p.page, p.rowsPerPage);
return Ok(l);
}
//Helpers
public class QueryParams
{
public bool descending { get; set; }
public int page { get; set; } = 1;
public int rowsPerPage { get; set; } = 5;
public string sortBy { get; set; }
public onject filter_value { get; set; }
public string filter_field { get; set; }
public string filter_mode { get; set; }
}
public class Pager<T>
{
public int pages { get; set; }
public int total { get; set; }
public IEnumerable<T> Items { get; set; }
public Pager(IEnumerable<T> items, int offset, int limit)
{
Items = items.Skip((offset - 1) * limit).Take(limit).ToList<T>();
total = items.Count();
pages = (int)Math.Ceiling((double)total / limit);
}
}
Assuming all you have is the entity type and strings representing the property, comparison operator and the value, building dynamic predicate can be done with something like this:
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, string value)
{
var parameter = Expression.Parameter(typeof(T), "x");
var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.Property);
var body = MakeComparison(left, comparison, value);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
private static Expression MakeComparison(Expression left, string comparison, string value)
{
switch (comparison)
{
case "==":
return MakeBinary(ExpressionType.Equal, left, value);
case "!=":
return MakeBinary(ExpressionType.NotEqual, left, value);
case ">":
return MakeBinary(ExpressionType.GreaterThan, left, value);
case ">=":
return MakeBinary(ExpressionType.GreaterThanOrEqual, left, value);
case "<":
return MakeBinary(ExpressionType.LessThan, left, value);
case "<=":
return MakeBinary(ExpressionType.LessThanOrEqual, left, value);
case "Contains":
case "StartsWith":
case "EndsWith":
return Expression.Call(MakeString(left), comparison, Type.EmptyTypes, Expression.Constant(value, typeof(string)));
default:
throw new NotSupportedException($"Invalid comparison operator '{comparison}'.");
}
}
private static Expression MakeString(Expression source)
{
return source.Type == typeof(string) ? source : Expression.Call(source, "ToString", Type.EmptyTypes);
}
private static Expression MakeBinary(ExpressionType type, Expression left, string value)
{
object typedValue = value;
if (left.Type != typeof(string))
{
if (string.IsNullOrEmpty(value))
{
typedValue = null;
if (Nullable.GetUnderlyingType(left.Type) == null)
left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type));
}
else
{
var valueType = Nullable.GetUnderlyingType(left.Type) ?? left.Type;
typedValue = valueType.IsEnum ? Enum.Parse(valueType, value) :
valueType == typeof(Guid) ? Guid.Parse(value) :
Convert.ChangeType(value, valueType);
}
}
var right = Expression.Constant(typedValue, left.Type);
return Expression.MakeBinary(type, left, right);
}
}
Basically building property accessor (with nested property support), parsing the comparison operator and calling the corresponding operator/method, dealing with from/to string and from/to nullable type conversions. It can be extended to handle EF Core specific functions like EF.Functions.Like by adding the corresponding branch.
It can be used directly (in case you need to combine it with other predicates) or via custom extension method like this:
public static partial class QueryableExtensions
{
public static IQueryable<T> Where<T>(this IQueryable<T> source, string propertyName, string comparison, string value)
{
return source.Where(ExpressionUtils.BuildPredicate<T>(propertyName, comparison, value));
}
}
based on Ivans answer this is what i came up with
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, object value)
{
var parameter = Expression.Parameter(typeof(T));
var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.PropertyOrField);
var body = MakeComparison(left, comparison, value);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
static Expression MakeComparison(Expression left, string comparison, object value)
{
var constant = Expression.Constant(value, left.Type);
switch (comparison)
{
case "==":
return Expression.MakeBinary(ExpressionType.Equal, left, constant);
case "!=":
return Expression.MakeBinary(ExpressionType.NotEqual, left, constant);
case ">":
return Expression.MakeBinary(ExpressionType.GreaterThan, left, constant);
case ">=":
return Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, left, constant);
case "<":
return Expression.MakeBinary(ExpressionType.LessThan, left, constant);
case "<=":
return Expression.MakeBinary(ExpressionType.LessThanOrEqual, left, constant);
case "Contains":
case "StartsWith":
case "EndsWith":
if (value is string)
{
return Expression.Call(left, comparison, Type.EmptyTypes, constant);
}
throw new NotSupportedException($"Comparison operator '{comparison}' only supported on string.");
default:
throw new NotSupportedException($"Invalid comparison operator '{comparison}'.");
}
}
}
and some tests
public class Tests
{
[Fact]
public void Nested()
{
var list = new List<Target>
{
new Target
{
Member = "a"
},
new Target
{
Member = "bb"
}
};
var result = list.AsQueryable()
.Where(ExpressionUtils.BuildPredicate<Target>("Member.Length", "==", 2))
.Single();
Assert.Equal("bb", result.Member);
}
[Fact]
public void Field()
{
var list = new List<TargetWithField>
{
new TargetWithField
{
Field = "Target1"
},
new TargetWithField
{
Field = "Target2"
}
};
var result = list.AsQueryable()
.Where(ExpressionUtils.BuildPredicate<TargetWithField>("Field", "==", "Target2"))
.Single();
Assert.Equal("Target2", result.Field);
}
[Theory]
[InlineData("Name", "==", "Person 1", "Person 1")]
[InlineData("Name", "!=", "Person 2", "Person 1")]
[InlineData("Name", "Contains", "son 2", "Person 2")]
[InlineData("Name", "StartsWith", "Person 2", "Person 2")]
[InlineData("Name", "EndsWith", "son 2", "Person 2")]
[InlineData("Age", "==", 13, "Person 2")]
[InlineData("Age", ">", 12, "Person 2")]
[InlineData("Age", "!=", 12, "Person 2")]
[InlineData("Age", ">=", 13, "Person 2")]
[InlineData("Age", "<", 13, "Person 1")]
[InlineData("Age", "<=", 12, "Person 1")]
public void Combos(string name, string expression, object value, string expectedName)
{
var people = new List<Person>
{
new Person
{
Name = "Person 1",
Age = 12
},
new Person
{
Name = "Person 2",
Age = 13
}
};
var result = people.AsQueryable()
.Where(ExpressionUtils.BuildPredicate<Person>(name, expression, value))
.Single();
Assert.Equal(expectedName, result.Name);
}
}
I modified the answer I found here: Linq WHERE EF.Functions.Like - Why direct properties work and reflection does not?
I chucked together a version for those using NpgSQL as their EF Core provider as you will need to use the ILike function instead if you want case-insensitivity, also added a second version which combines a bunch of properties into a single Where() clause:
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, string propertyName, string searchTerm)
{
// Check property name
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentNullException(nameof(propertyName));
}
// Check the search term
if(string.IsNullOrEmpty(searchTerm))
{
throw new ArgumentNullException(nameof(searchTerm));
}
// Check the property exists
var property = typeof(T).GetProperty(propertyName);
if (property == null)
{
throw new ArgumentException($"The property {typeof(T)}.{propertyName} was not found.", nameof(propertyName));
}
// Check the property type
if(property.PropertyType != typeof(string))
{
throw new ArgumentException($"The specified property must be of type {typeof(string)}.", nameof(propertyName));
}
// Get expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
// Build the property expression and return it
Expression selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
}
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, IEnumerable<string> propertyNames, string searchTerm)
{
// Check property name
if (!(propertyNames?.Any() ?? false))
{
throw new ArgumentNullException(nameof(propertyNames));
}
// Check the search term
if (string.IsNullOrEmpty(searchTerm))
{
throw new ArgumentNullException(nameof(searchTerm));
}
// Check the property exists
var properties = propertyNames.Select(p => typeof(T).GetProperty(p)).AsEnumerable();
if (properties.Any(p => p == null))
{
throw new ArgumentException($"One or more specified properties was not found on type {typeof(T)}: {string.Join(",", properties.Where(p => p == null).Select((p, i) => propertyNames.ElementAt(i)))}.", nameof(propertyNames));
}
// Check the property type
if (properties.Any(p => p.PropertyType != typeof(string)))
{
throw new ArgumentException($"The specified properties must be of type {typeof(string)}: {string.Join(",", properties.Where(p => p.PropertyType != typeof(string)).Select(p => p.Name))}.", nameof(propertyNames));
}
// Get the expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
// Build the expression and return it
Expression selectorExpression = null;
foreach (var property in properties)
{
var previousSelectorExpression = selectorExpression;
selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
if(previousSelectorExpression != null)
{
selectorExpression = Expression.Or(previousSelectorExpression, selectorExpression);
}
}
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
}

GWT: multiple column sorting

I am trying to do multiple columns sorting in my application .
Like i have firstname , last name columns
Right now , when i click on firstname header , it sorts as per firstname , when i click on lastname column it sorts as per lastname column..
what i need is when i click on firstname header it should sort on the basis of firstname and then if i click on lastname(with shift or any other option) header it should sort on the basis of both firstname and lastname , firstname as primary column and last name as sub sorting column
here is what i have now
private void sortTableUsers(List<UserDTO> userList){
ListDataProvider<UserDTO> dataProvider = new ListDataProvider<UserDTO>();
dataProvider.addDataDisplay(usersTable);
List<UserDTO> list = dataProvider.getList();
for (UserDTO UserDTO : userList) {
list.add(UserDTO);
}
final ListHandler<UserDTO> columnSortHandler = new ListHandler<UserDTO>(list);
columnSortHandler.setComparator(firstNameColumn,new Comparator<UserDTO>() {
public int compare(UserDTO o1,UserDTO o2) {
if (o1 == o2) {
return 0;
}
// Compare the firstname columns.
if (o1 != null) {
return (o2 != null) ? o1.getUser().getFirstName().compareTo(o2.getUser().getFirstName()) : 1;
}
return -1;
}
});
columnSortHandler.setComparator(lastNameColumn,new Comparator<UserDTO>() {
public int compare(UserDTO o1,UserDTO o2) {
if (o1 == o2) {
return 0;
}
// Compare the lastname columns.
if (o1 != null) {
return (o2 != null) ? o1.getUser().getLastName().compareTo(o2.getUser().getLastName()) : 1;
}
return -1;
}
});
usersTable.getColumnSortList().push(firstNameColumn);
usersTable.getColumnSortList().push(middleNameColumn);
}
Well, you need a different comparator for each column, the second comparator is the one that you need to change.
First it must sort for FirstName,and if the firstnames are equal, then go on and compare the last names too.
I'm not using your DTO, and i don't check for nulls, but it's the same thing, you get the idea
ArrayList<Map> list = new ArrayList<Map>();
ListHandler<Map> _sortHandler = new ListHandler<Map>(list);
Column columnDefinitionFirstName = null; // create your column first name
columnDefinitionFirstName.setSortable(true);
//
_sortHandler.setComparator(columnDefinitionFirstName, new Comparator<Map>()
{
public int compare(Map o1, Map o2)
{
int res = 0;
String object1 = (String) o1.get("FIRST_NAME");
String object2 = (String) o2.get("FIRST_NAME");
res = object1.compareTo(object2);
return res;
}
});
Column columnDefinitionLastName = null; // create your column last name
columnDefinitionLastName.setSortable(true);
_sortHandler.setComparator(columnDefinitionLastName, new Comparator<Map>()
{
public int compare(Map o1, Map o2)
{
int res = 0;
String object1 = (String) o1.get("FIRST_NAME");
String object2 = (String) o2.get("FIRST_NAME");
res = object1.compareTo(object2);
if(res == 0)
{
String object11 = (String) o1.get("LAST_NAME");
String object22 = (String) o2.get("LAST_NAME");
res = object11.compareTo(object22);
}
return res;
}
});