I want to sort multiple columns with Linq
I used this link for reference, which is used for sorting of single column by column name.
I am trying to use this method for sorting of multiple columns with column names.
Here is what i am doing so far
public static IQueryable<T> OrderByMultipleFields<T>(this IQueryable<T> q, Dictionary<string, bool> fieldsToSort)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, fieldsToSort.First().Key);
var exp = Expression.Lambda(prop, param);
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
string method=string.Empty;
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = q.Expression;
int count = 0;
foreach (var fieldName in fieldsToSort)
{
method = fieldName.Value ? methodAsc : methodDesc;
prop = Expression.Property(param, fieldName.Key);
exp = Expression.Lambda(prop, param);
types = new Type[] { q.ElementType, exp.Body.Type };
if (count == 0) {
mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
} else {
mce = Expression.Add(mce, Expression.Call(typeof(Queryable), method, types, q.Expression, exp));
}
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
count++;
}
return q.Provider.CreateQuery<T>(mce);
}
I am getting following error -
The binary operator Add is not defined for the types
'System.Linq.IOrderedQueryable1[SortDemo.Data.User]' and
'System.Linq.IOrderedQueryable1[SortDemo.Data.User]'.
what is the proper way to achieve this or is there any alternate approach or method for this.
thanks.
It's not clear why you're trying to call Add in the first place. There are no addition operations in normal sorting code, so there wouldn't be in the expression tree form either.
I suspect you just want to replace this:
if (count == 0)
{
mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
}
else {
mce = Expression.Add(mce, Expression.Call(typeof(Queryable), method, types, q.Expression, exp));
}
with:
mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
I'd also suggest moving the declarations of method, types, exp and props to inside the loop, as well... and removing count. That would leave you with:
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
var mce = q.Expression;
foreach (var fieldName in fieldsToSort)
{
var method = fieldName.Value ? methodAsc : methodDesc;
var prop = Expression.Property(param, fieldName.Key);
var exp = Expression.Lambda(prop, param);
var types = new Type[] { q.ElementType, exp.Body.Type };
mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
return q.Provider.CreateQuery<T>(mce);
I haven't tried it but it's more likely to worth than what you had before.
Another thing to consider is not building up the whole expression tree like this, but instead calling the appropriate Queryable methods directly yourself via reflection. You'd then only need to build up a single property-access expression tree at any point.
Related
I want to create a generic filter that behaves like Queryable.Where but accepts string and translates it to properties.
foreach (var key in Request.QueryString.AllKeys)
{
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(qstr[key]))
{
//the key/property can be direct like Employee.Name or indirect like Employee.Boss.LastName etc
source = source.GenerateFilter<Employee>(key, qstr[key]);
}
}
This is my code so far:
public static IQueryable<TEntity> GenerateFilter<TEntity>(this IQueryable<TEntity> source, string propertyName, string term) where TEntity : class
{
var parameter = Expression.Parameter(typeof(TEntity), "Entity");
// create the selector part and support child properties
PropertyInfo property;
Expression propertyAccess;
if (propertyName.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = propertyName.Split('.');
property = typeof(TEntity).GetProperty(childProperties[0], BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
for (int i = 1; i < childProperties.Length; i++)
{
property = property.PropertyType.GetProperty(childProperties[i], BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
}
}
else
{
property = typeof(TEntity).GetProperty(propertyName);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
}
//term is the filter value for this property
ConstantExpression queryExpression = Expression.Constant(term.ToUpper(), typeof(string));
//i don't want an exact match so i have to call StartsWith
MethodInfo methodCallExpression = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
MethodCallExpression call = Expression.Call(propertyAccess, methodCallExpression, queryExpression);
var lambda = Expression.Lambda<Func<TEntity, bool>>(call, parameter);
Type type = typeof(TEntity);
MethodInfo wheremethod = typeof(Queryable).GetMethod("Where", new Type[] { typeof(Expression<Func<TEntity, bool>>) });
MethodCallExpression call2 = Expression.Call(lambda, wheremethod);
return source.Provider.CreateQuery<TEntity>(call2);
//return source.Where(lambda);
}
Does't work and I am trying to find the correct syntax to build this expression. Any ideas?
I am trying to build a filter method for IQueryable Type,
I could achieve this for ex:
query = query.Filter(UsersListId, e => e.UserId);
public static IQueryable<T> Filter<T, TSearch>(this IQueryable<T> query, List<TSearch> list, Expression<Func<T, TSearch>> props)
{
if (list == null || list.Count == 0)
{
return query;
}
var propertyPath = props.Body.ToString().Replace(props.Parameters[0] + ".", string.Empty);
var containsMethod = typeof(List<TSearch>).GetMethod("Contains", new Type[] { typeof(TSearch) });
ConstantExpression constlist = Expression.Constant(list);
var param = Expression.Parameter(typeof(T), "Entity");
var newvalue = GetPropertyValue(param, propertyPath);
var body = Expression.Call(constlist, containsMethod, newvalue);
var exp = Expression.Lambda<Func<T, bool>>(body, param);
return query.Where(exp);
}
public static MemberExpression GetPropertyValue(ParameterExpression parExp, string propertyPath)
{
var properties = propertyPath.Split('.').ToArray();
var value = Expression.Property(parExp, properties[0]);
for (int i = 1; i < properties.Length; i++)
{
value = Expression.Property(value, properties[i]);
}
return value;
}
this code takes the propertyPath (ex: x.User.Department.Id)
and returns the value,
The problems here:
I cannot pass a nullable object if the list isn't nullable too
I am not quite sure weather it's the right way to access properties
so my question is what is the solution for these problems?
In my project, I want the user to be able to supply an argument in the shape of: "Fieldname=Value"; I will then check the arguments supplied, and check if the Fieldname is a valid property in a Model; then, I need to have a Linq.Where() query that dynamically selects the required Fieldname for the filtering:
start.exe -Filter "TestField=ThisValue"
should translate to:
List<Mutations>.Where(s => s.{TestField} == "ThisValue" ).FirstOrDefault
the problem is that I do not know how I convert a string, or a name of a property, to the s.{TestField} part..
You can use the Reflection and Expression APIs for this. To start, I am going to assume that you are actually using properties and not fields (you are using properties, right?)
var type = typeof(Mutations);
var member = type.GetProperty("TestProperty");
if (member == null)
throw new Exception("property does not exist");
var p1 = Expression.Parameter(type, "s");
var equal = Expression.Equal(
Expression.Property(p1, member),
Expression.Constant("Test Value", member.PropertyType)
);
var lambda = Expression.Lambda<Func<Mutations, bool>>(equal, p1);
var result = list.AsQueryable().FirstOrDefault(lambda);
If you are actually using public fields (why?!) you can make the following modifications GetProperty->GetField, Expression.Property->Expression.Field and member.PropertyType->member.FieldType. Use caution though; some ORMs only work with properties and thus would reject the otherwise valid Expression.
We can take the above and turn it into a reusable, generic method that returns an Expression:
using System.Linq.Expressions;
public static class ExpressionHelpers {
public static Expression CreateWhere<T>(string propertyName, string targetValue) {
var type = typeof(T);
var member = type.GetProperty(propertyName) ?? throw new Exception("Property does not exist");
var p1 = Expression.Parameter(type, "s");
var equal = Expression.Equal(
Expression.Property(p1, member),
Expression.Constant(targetValue, member.PropertyType)
);
return Expression.Lambda<Func<T, bool>>(equal, p1);
}
}
Calling this method might look like:
public static void SomeMethod() {
var list = new List<Mutations> { /* ... */ };
Expression clause = ExpressionHelpers.CreateWhere<Mutations>("TestProperty", "TestValue");
var result = list.AsQueryable().FirstOrDefault(clause);
if (result != null)
Console.WriteLine("Result = {0}", result);
}
Note that this doesn't do any sort of validation of the data types--it assumes the property is the same type as the input, which is currently string. If you need to deal with numbers or dates or what have you, you'll need to switch on the data type and provide the appropriate parsed data to the constant:
public static Expression CreateWhere<T>(string propertyName, string targetValue) {
var type = typeof(T);
var member = type.GetProperty(propertyName) ?? throw new Exception("Property does not exist");
var propType = member.PropertyType;
if ((propType.IsClass && propType != typeof(string)) || propType.IsInterface)
throw new Exception("Interfaces and Class Types are not supported");
var p1 = Expression.Parameter(type, "s");
Expression target = null;
if (propType == typeof(string))
target = Expression.Constant(targetValue, typeof(string));
else if (propType == typeof(int) && int.TryParse(targetValue, out var intVal))
target = Expression.Constant(intVal, typeof(int));
else if (propType == typeof(long) && long.TryParse(targetValue, out var longVal))
target = Expression.Constant(longVal, typeof(long));
else if (propType == typeof(DateTime) && DateTime.TryParse(targetValue, out var dateVal))
target = Expression.Constant(dateVal, typeof(DateTime));
else
throw new Exception("Target property type is not supported or value could not be parsed");
var equal = Expression.Equal(
Expression.Property(p1, member),
target
);
return Expression.Lambda<Func<T, bool>>(equal, p1);
}
As you can see this starts to get fairly complex with the more types you want to support. Also note that if you are using this without an ORM (just LINQ on a list) you are probably going to want add some support for case-[in]sensitive string comparisons. That can be delegated to a string.Equals call that could look something like this:
bool ignoreCase = true; // maybe a method parameter?
var prop = Expression.Property(p1, member);
Expression equal = null;
if (propType != typeof(string))
{
equal = Expression.Equal(prop, target);
}
else
{
var compareType = ignoreCase
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
var compareConst = Expression.Constant(compareType, typeof(StringComparison));
equal = Expression.Call(
typeof(string),
nameof(string.Equals),
new[] { typeof(string), typeof(string), typeof(StringComparison) },
prop,
target,
compareConst
);
}
return Expression.Lambda<Func<T, bool>>(equal, p1);
Note that depending on their support this may or may not work with an ORM (and may not be necessary since many databases are insensitive comparisons by default). The above also does not handle Nullable<T> (ie. int?) which adds its own set of complexities.
I want to create a dynamic filter for my repositories using linq expressions, I have other filters but i don't know how to make the next one using expressions: (the condition was taked from here)
var result = _studentRepotory.GetAll().Where(s =>
SqlFunctions.StringConvert((double)s.Id).Contains("91")).ToList();
I have a method that receives the value of a property, the propertyName and the filter operator type (enum):
public static class Helper {
public static Expression<Func<T, bool>> GetExpression<T>(object value, string propertyName, FilterOperatorType FilterType)
{
var parameter = Expression.Parameter(typeof(T));
var property = ExpressionUtils.GetPropertyChild(parameter, propertyName);
var constValue = Expression.Constant(value);
BinaryExpression expresion = null;
switch (FilterType)
{
case FilterOperatorType.Equal:
expresion = Expression.Equal(property, constValue);
break;
case FilterOperatorType.Greater:
expresion = Expression.GreaterThan(property, constValue);
break;
case FilterOperatorType.Less:
expresion = Expression.LessThan(property, constValue);
break;
case FilterOperatorType.GreaterOrEqual:
expresion = Expression.GreaterThanOrEqual(property, constValue);
break;
case FilterOperatorType.LessOrEqual:
expresion = Expression.LessThanOrEqual(property, constValue);
break;
}
var lambda = Expression.Lambda<Func<T, bool>>(expresion, parameter);
return lambda;
}
}
So, I want to add a new opertator type Contains that will evaluate if an integer contains some digits, in the first block of code I do it, but I want to do it with linq expressions using generics.
At the end I wil have:
Expression<Func<Student, bool>> where = Helper.GetExpression<Student>("91", "Id", FilterOperatorType.Contains);
var result = _studentRepotory.GetAll().Where(where).ToList();
The query should return all the students when the Id contains the digits 91.
Please help me, and tell me if you understand.
I'm at 2:00 am and still working on this, the brain works better at that time hehehe, here is the solution for create the expression:
object value = "91";
var parameter = Expression.Parameter(typeof(Student));
var property = ExpressionUtils.GetPropertyChild(parameter, "Id");
var constValue = Expression.Constant(value);
var expressionConvert = Expression.Convert(property, typeof(double?));
var methodStringConvert = typeof(SqlFunctions).GetMethod("StringConvert",
BindingFlags.Public | BindingFlags.Static, null,
CallingConventions.Any,
new Type[] { typeof(double?) },
null);
var methodContains = typeof(string).GetMethod("Contains",
BindingFlags.Public | BindingFlags.Instance,
null, CallingConventions.Any,
new Type[] { typeof(String) }, null);
var expresionStringConvert = Expression.Call(methodStringConvert, expressionConvert);
var expresionContains = Expression.Call(expresionStringConvert, methodContains, constValue);
var lambdaContains = Expression.Lambda<Func<Student, bool>>(expresionContains, parameter);
Now you can use lambdaContains in the Where method of the studentRepository
Here is a method. And you can search int columns as strings
https://gist.github.com/gnncl/e424adefc3e08b607beee8d638759492
I am using below code for Generic Filter, any search text passed but the contains method is Case sensitive, how can I write to ignore case.
public static class QueryExtensions
{
public static IQueryable<T> Filter<T>(this IQueryable<T> query, string search)
{
var properties = typeof(T).GetProperties().Where(p =>
/*p.GetCustomAttributes(typeof(System.Data.Objects.DataClasses.EdmScalarPropertyAttribute),true).Any() && */
p.PropertyType == typeof(String));
var predicate = PredicateBuilder.False<T>();
foreach (var property in properties )
{
predicate = predicate.Or(CreateLike<T>(property,search));
}
return query.AsExpandable().Where(predicate);
}
private static Expression<Func<T,bool>> CreateLike<T>( PropertyInfo prop, string value)
{
var parameter = Expression.Parameter(typeof(T), "f");
var propertyAccess = Expression.MakeMemberAccess(parameter, prop);
var like = Expression.Call(propertyAccess, "Contains", null, Expression.Constant(value,typeof(string)));
return Expression.Lambda<Func<T, bool>>(like, parameter);
}
}
Instead of calling String.Contains, call String.IndexOf with a case insensitive StringComparison parameter. Then compare its result with 0, with the Expression.GreaterThanOrEqual expression. You need to provide the extra parameter in your Expression.Call as an Expression.Constant.
You can decide to hardcode one of the case-insensitive StringComparison options, or export it as a parameter of the Filter method, allowing users to decide whether they want case-insensitive search or not.
You can do something like this:
private static Expression<Func<T, bool>> CreateLike<T>(PropertyInfo prop, string value)
{
var parameter = Expression.Parameter(typeof(T), "f");
var propertyAccess = Expression.MakeMemberAccess(parameter, prop);
var indexOf = Expression.Call(propertyAccess, "IndexOf", null, Expression.Constant(value, typeof(string)),Expression.Constant(StringComparison.InvariantCultureIgnoreCase));
var like=Expression.GreaterThanOrEqual(indexOf, Expression.Constant(0));
return Expression.Lambda<Func<T, bool>>(like, parameter);
}
or, with the StringComparison parameter
private static Expression<Func<T, bool>> CreateLike<T>(PropertyInfo prop,
string value,
StringComparison comparison=StringComparison.InvariantCultureIgnoreCase)
{
var parameter = Expression.Parameter(typeof(T), "f");
var propertyAccess = Expression.MakeMemberAccess(parameter, prop);
var indexOf = Expression.Call(propertyAccess, "IndexOf", null,
Expression.Constant(value, typeof(string)),
Expression.Constant(comparison));
var like=Expression.GreaterThanOrEqual(indexOf, Expression.Constant(0));
return Expression.Lambda<Func<T, bool>>(like, parameter);
}
By using a default value for comparison you avoid creating two overloads for the same job.
You could try using String.IndexOf instead.
string x,y = string.Empty;
x.IndexOf(y,0,x.Length, StringComparison.CurrentCultureIgnoreCase) > -1
As it has a StringComparison parameter.
This would return an integer
var like = Expression.Call(propertyAccess, "IndexOf", null, Expression.Constant(value, typeof(string)), Expression.Constant(StringComparison.CurrentCultureIgnoreCase,typeof(StringComparison)));
Please refer to the following code if you want to filter or search for a value from the list. In addition, it is a generic method that will help you filter any type of class or object from the list. It is working as a like clause in SQL such as (column1 like '%abc%' or column2 like '%abc%').
public static class Filter<T>
{
public static Expression<Func<T, bool>> FilterExpression(string searchValue)
{
Expression finalExpression = Expression.Constant(false);
var parameter = Expression.Parameter(typeof(T), "x");
PropertyInfo[] propertyInfos = typeof(T).GetProperties();
foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (propertyInfo.PropertyType == typeof(string))
{
var propertyExpn = Expression.Property(parameter, propertyInfo.Name.Trim().ToLower());
var containsExpn = Expression.Call(propertyExpn, "Contains", null, Expression.Constant(searchValue, typeof(string)), Expression.Constant(StringComparison.InvariantCultureIgnoreCase));
var nullCheckExpn = Expression.NotEqual(propertyExpn, Expression.Constant(null, typeof(string)));
var andAlsoExpn = Expression.AndAlso(nullCheckExpn, containsExpn);
finalExpression = Expression.Or(finalExpression, andAlsoExpn);
}
}
var rowFilterLambdaExpression = Expression.Lambda<Func<T, bool>>(finalExpression, new ParameterExpression[] { parameter });
return rowFilterLambdaExpression;
}
}
Usage E.g.,
var result =
dataItems.Where(Filter<T>.FilterExpression(model.FilterValue).Compile()).ToList();
It's probably simplest to convert both parameters to upper case first (upper case conversions are optimized better than lower case ones in .NET). You can then do your comparison.
Upper case conversion can be done like this:
var expression = Expression.Call(property, typeof(string).GetMethod("ToUpperInvariant", System.Type.EmptyTypes));