Generic LINQ Where clause - c#

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?

Related

C# Expression, Accessing Property

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?

C# Using dynamic Linq expressions filter a column that is of type int using Startswith and getting Nullable`1 for conversion

As a basis for filtering using dynamic linq I used this answer and it works for columns of type String however I also needed to do startsWith/Contains on integer type columns as well - in this case I used this answer - and it falls over with the error shown a little below.
Here is the code that creates the IQueryable result.
public static IQueryable<T> Has<T>(this IQueryable<T> source, ExpressionFilter filter) {
if (source == null || filter.PropertyName.IsNull() || filter.Value == null) {
return source;
}
// Find the type incase we get an int column.
Type propertyType = source.ElementType.GetProperty(filter.PropertyName).PropertyType;
// For our Call.
MethodCallExpression methodCallExpression = null;
// If its a string we need to change it to lower case.
MethodCallExpression toLowerExpression = null;
// If its any one of the binary expressions.
Expression binaryExpression = null;
// ..and our predicate is initiated here as well.
Expression<Func<T, bool>> predicate = null;
// We need the parameter eg x => ..
ParameterExpression parameter = Expression.Parameter(source.ElementType, "x");
// Finally here is our Property expression eg x => LastName Last name being the property name
Expression propertyExp = Expression.Property(parameter, filter.PropertyName);
// our METHODINFO's
var CONTAINS_METHOD = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var STARTS_WITH_METHOD = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var ENDS_WITH_METHOD = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
var BOOL_EQUAL_METHOD = typeof(bool).GetMethod("Equals", new Type[] { typeof(bool) });
var TO_LOWER_METHOD = typeof(string).GetMethod("ToLower", new Type[] { });
var TRIM_START = typeof(string).GetMethod("Trim", new Type[] { typeof(int) });
var STRING_CONVERT = typeof(SqlFunctions).GetMethod("StringConvert", new[] { typeof(double?) }); ////get the SqlFunctions.StringConvert method for nullable double
// We supply a type of object for our term to search on.. it needs to be a string.
ConstantExpression termConstant = Expression.Constant(filter.Value.ToString(), typeof(string));
// In case we get a propertyType of (int) we cant just perform a lower case on it.
if (propertyType == typeof(string)) {
toLowerExpression = Expression.Call(propertyExp, TO_LOWER_METHOD);
}
switch (filter.Comparison) {
case Comparison.Contains:
methodCallExpression = Expression.Call(toLowerExpression, CONTAINS_METHOD, termConstant);
break;
case Comparison.StartsWith:
if (propertyType == typeof(int)) { // We'll do a startsWith on an int column
//convert Expression to a nullable double (or nullable decimal),
//so that you can use SqlFunctions.StringConvert
propertyExp = Expression.Convert(propertyExp, typeof(double?));
//call StringConvert on your converted expression
propertyExp = Expression.Call(null, STRING_CONVERT, propertyExp);
methodCallExpression = Expression.Call(propertyExp, STARTS_WITH_METHOD, termConstant);
}
else
methodCallExpression = Expression.Call(toLowerExpression, STARTS_WITH_METHOD, termConstant); //WORKS HERE..
break;
case Comparison.EndsWith:
methodCallExpression = Expression.Call(toLowerExpression, ENDS_WITH_METHOD, termConstant);
break;
case Comparison.BoolTest:
bool parsedBoolValue;
if (bool.TryParse(filter.Value.ToString().ToLower(), out parsedBoolValue)) { // Its a bool column.
termConstant = Expression.Constant(parsedBoolValue, typeof(bool));
methodCallExpression = Expression.Call(propertyExp, BOOL_EQUAL_METHOD, termConstant);
}
break;
case Comparison.Equal:
binaryExpression = Expression.Equal(propertyExp, termConstant);
break;
case Comparison.GreaterThan:
binaryExpression = Expression.GreaterThan(propertyExp, termConstant);
break;
case Comparison.GreaterThanOrEqual:
binaryExpression = Expression.GreaterThanOrEqual(propertyExp, termConstant);
break;
case Comparison.LessThan:
binaryExpression = Expression.LessThan(propertyExp, termConstant);
break;
case Comparison.LessThanOrEqual:
binaryExpression = Expression.LessThanOrEqual(propertyExp, termConstant);
break;
case Comparison.NotEqual:
binaryExpression = Expression.NotEqual(propertyExp, termConstant);
break;
case Comparison.IndexOf:
binaryExpression = Expression.NotEqual(
Expression.Call(
propertyExp,
"IndexOf",
null,
Expression.Constant(filter.Value, typeof(string)),
Expression.Constant(StringComparison.InvariantCultureIgnoreCase, typeof(StringComparison))
),
Expression.Constant(-1, typeof(int))
);
break;
default:
return null;
}
if (binaryExpression == null) {
predicate = Expression.Lambda<Func<T, bool>>(methodCallExpression, parameter);
}
else {
predicate = Expression.Lambda<Func<T, bool>>(binaryExpression, parameter);
}
methodCallExpression = Expression.Call(typeof(Queryable), "Where",
new Type[] { source.ElementType },
source.Expression, Expression.Quote(predicate));
return source.Provider.CreateQuery<T>(methodCallExpression);
}
So it works with columns of string type but when you try and do a filter on an int column it fails with..
"Operation is not valid due to the current state of the object."
For an int column it builds the expression with:
if (propertyType == typeof(int)) { // We'll do a startsWith on an int column
//convert Expression to a nullable double (or nullable decimal),
//so that you can use SqlFunctions.StringConvert
propertyExp = Expression.Convert(propertyExp, typeof(double?));
//call StringConvert on your converted expression
propertyExp = Expression.Call(null, STRING_CONVERT, propertyExp);
methodCallExpression = Expression.Call(propertyExp, STARTS_WITH_METHOD, termConstant);
}
and as an expression this yields
{x => StringConvert(Convert(x.ClientNo,
Nullable`1)).StartsWith("101")}
I could be wrong here but why would it put a back tick and a "1" after nullable?? is this why it falls over?
For completeness at the point it completes the "Has" method (adds to the expression tree) and this is the result (which fails):
Is there a better way to construct this expression so it works?

Integer contains linq c# but using using expressions

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

Linq sorting with multiple column names in c#

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.

Call Ignore Case for Contains Method using a generic LINQ Expression

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

Categories