I am trying to create a reusable method using expressions that looks something like this:
Expression<Func<Order, bool>> CreateExpression(Expression<Func<Order, int>> parameter, FilterOperator operator, int value)
So I can use it like this:
IQueryable<Order> orders = db.Orders;
var filtered = orders.Where(CreateExpression(o => o.OrderID, FilterOperator.GreaterThan, 100));
I'm not sure how to write the method though. How can I write a method that will create this Expression for me?
I need to be able to do something like this:
if(operator == FilterOperator.GreaterThan)
return m => m.OrderID > value;
else if(operator == FilterOperator.LessThan)
return m => m.OrderID < value;
But I want to use the expression that is passed in instead of using OrderID directly. How can I do this?
static Expression<Func<T, bool>> CreateExpression<T>(Expression<Func<T, int>> parameter, FilterOperator #operator, int value)
{
var argExpr = Expression.Parameter(typeof(T), "p");
var paramExpr = Expression.Invoke(parameter, argExpr);
var constExpr = Expression.Constant(value);
Expression compExpr = null;
switch(#operator)
{
case FilterOperator.GreaterThan:
compExpr = Expression.GreaterThan(paramExpr, constExpr);
break;
case FilterOperator.LessThan:
compExpr = Expression.LessThan(paramExpr, constExpr);
break;
}
return Expression.Lambda<Func<T, bool>>(compExpr, argExpr);
}
If you can't use Invoke and your parameter expression is a member expression, then you can just re-create it using your new parameter:
static Expression<Func<T, bool>> CreateExpression<T>(Expression<Func<T, int>> parameter, FilterOperator #operator, int value)
{
var memberExpr = (MemberExpression)parameter.Body;
PropertyInfo property = (PropertyInfo)memberExpr.Member;
var argExpr = Expression.Parameter(typeof(T), "p");
var propertyExpr = Expression.Property(argExpr, property);
var constExpr = Expression.Constant(value);
Expression compExpr = null;
switch(#operator)
{
case FilterOperator.GreaterThan:
compExpr = Expression.GreaterThan(propertyExpr, constExpr);
break;
case FilterOperator.LessThan:
compExpr = Expression.LessThan(propertyExpr, constExpr);
break;
}
return Expression.Lambda<Func<T, bool>>(compExpr, argExpr);
}
Related
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?
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?
I'm having trouble coding an expression for IQueryable object. I can't figure out the right way to use the expression to map a string to object properties.
this is my query object and mapping dictionary:
var query = context.Industries.AsQueryable();
var columnsMap = new Dictionary<string, Expression<Func<Industry, object>>>()
{
["name"] = v => v.Name,
["isicCode"] = v => v.IsicCode.Data,
["isicCodeTitle"] = v => v.IsicCode.Title,
["isActive"] = v => v.IsActive,
};
and I'm using columnsMap dictionary for applying Orderby to my query in an extentions class:
public static IQueryable<T> ApplyOrdering<T>(this IQueryable<T> query, IQueryObject queryObj, Dictionary<string, Expression<Func<T, object>>> columnsMap)
{
if (String.IsNullOrWhiteSpace(queryObj.SortBy) || !columnsMap.ContainsKey(queryObj.SortBy))
return query;
if (queryObj.IsSortAsc)
return query.OrderBy(columnsMap[queryObj.SortBy]);
else
return query.OrderByDescending(columnsMap[queryObj.SortBy]);
}
it's ok for OrderBy but I need to do the same thing for ApplyFiltering but for filter an IQueryable object I need a different expression for where method Expression<Func<T, bool>>
public static IQueryable<T> ApplyFiltering<T>(this IQueryable<T> query, IQueryObject queryObj, Dictionary<string, Expression<Func<T, object>>> columnsMap)
{
query.Where(columnsMap['name'] == "test Name"); //this is the problem.
return query;
}
the question is how can I use my columnsMap in ApplyFiltering method? or should I change my columnsMap for that?
It is quite simple. The only problem is when you have a value type column (an int or a bool or a DateTime for example)... An Expression<Func<Industry, object>> will introduce a boxing of the field/property to object that we have to remove. This problem is absent for strings.
// isActive is a bool
Expression<Func<Industry, object>> exp = columnsMap["isActive"];
object value = true; // You can't use "true" (string) here! isActive is a bool
// Other exammple
// Expression<Func<Industry, object>> exp = columnsMap["name"];
// object value = "Foo";
var body = exp.Body;
// Remove the boxing for value types
if (body.NodeType == ExpressionType.Convert)
{
body = ((UnaryExpression)body).Operand;
}
var eq = Expression.Equal(body, Expression.Constant(value, body.Type));
var exp2 = Expression.Lambda<Func<T, bool>>(eq, exp.Parameters);
return query.Where(exp2);
Example using string value:
Expression<Func<Industry, object>> exp = columnsMap["isActive"];
string value = "true";
// Other exammple
//Expression<Func<Industry, object>> exp = columnsMap["name"];
//string value = "Foo";
var body = exp.Body;
// Remove the boxing for value types
if (body.NodeType == ExpressionType.Convert)
{
body = ((UnaryExpression)body).Operand;
}
object value2 = value;
if (value2 != null && body.Type != value2.GetType())
{
value2 = Convert.ChangeType(value2, body.Type);
}
var eq = Expression.Equal(body, Expression.Constant(value2, body.Type));
var exp2 = Expression.Lambda<Func<Industry, bool>>(eq, exp.Parameters);
return query.Where(exp2);
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));
Okay, my guess is this is answered somewhere already, and I'm just not quite familiar enough with the syntax yet to understand, so bear with me.
The users of my web app need to filter a long list of items in a gridview, accessed via a linqdatasource. I am using the OnSelecting Event to further filter items. I want to filter those items based on selections the users make in DropDownLists.
For example, they select "Title" "Contains" "Fred"
This results in
e.Result = dbContext.Opps.Where(opp => opp.Title.Contains("Fred"));
Or "Description" "Does not Contain" "Alpha"
results in
e.Result = dbContext.Opps.Where(opp => !opp.Description.Contains("Alpha"));
I would like to build up that Expression (System.Linq.Expressions.Expression>) dynamically, instead of having nested switch expressions to generate it, as there are a number of fields I want to check, and I also want to use the StartsWith and EndsWith checks. If I could build the Expression as a string, like so:
string stringExpression = string.Format("opp => opp.{0}.{1}(\"{2}\")",
ddlCustomFilter.SelectedValue,
ddlFilterType.SelectedValue,
txtFilterText.Text);
And then somehow have it be converted into an Expression... is this possible? Or should I just bite the bullet and generate all the switch() statements required to create the various expressions?
You could certainly build the expression dynamically, but I would consider using Dynamic LINQ as an alternative first, though you may not be able to use Contains. In addition, you may want to consider using PredicateBuilder to build complex queries additively.
try this code...
call ToExpression Method()....
public static Expression<Func<T, bool>> ToExpression<T>(string andOrOperator, string propName, string opr, string value, Expression<Func<T, bool>> expr = null)
{
Expression<Func<T, bool>> func = null;
try
{
ParameterExpression paramExpr = Expression.Parameter(typeof(T));
var arrProp = propName.Split('.').ToList();
Expression binExpr = null;
string partName = string.Empty;
arrProp.ForEach(x =>
{
Expression tempExpr = null;
partName = partName.IsNull() ? x : partName + "." + x;
if (partName == propName)
{
var member = NestedExprProp(paramExpr, partName);
var type = member.Type.Name == "Nullable`1" ? Nullable.GetUnderlyingType(member.Type) : member.Type;
tempExpr = ApplyFilter(opr, member, Expression.Convert(ToExprConstant(type, value), member.Type));
}
else
tempExpr = ApplyFilter("!=", NestedExprProp(paramExpr, partName), Expression.Constant(null));
if (binExpr != null)
binExpr = Expression.AndAlso(binExpr, tempExpr);
else
binExpr = tempExpr;
});
Expression<Func<T, bool>> innerExpr = Expression.Lambda<Func<T, bool>>(binExpr, paramExpr);
if (expr != null)
innerExpr = (andOrOperator.IsNull() || andOrOperator == "And" || andOrOperator == "AND" || andOrOperator == "&&") ? innerExpr.And(expr) : innerExpr.Or(expr);
func = innerExpr;
}
catch { }
return func;
}
private static MemberExpression NestedExprProp(Expression expr, string propName)
{
string[] arrProp = propName.Split('.');
int arrPropCount = arrProp.Length;
return (arrPropCount > 1) ? Expression.Property(NestedExprProp(expr, arrProp.Take(arrPropCount - 1).Aggregate((a, i) => a + "." + i)), arrProp[arrPropCount - 1]) : Expression.Property(expr, propName);
}
private static Expression ToExprConstant(Type prop, string value)
{
if (value.IsNull())
return Expression.Constant(value);
object val = null;
switch (prop.FullName)
{
case "System.Guid":
val = value.ToGuid();
break;
default:
val = Convert.ChangeType(value, Type.GetType(prop.FullName));
break;
}
return Expression.Constant(val);
}
private static Expression ApplyFilter(string opr, Expression left, Expression right)
{
Expression InnerLambda = null;
switch (opr)
{
case "==":
case "=":
InnerLambda = Expression.Equal(left, right);
break;
case "<":
InnerLambda = Expression.LessThan(left, right);
break;
case ">":
InnerLambda = Expression.GreaterThan(left, right);
break;
case ">=":
InnerLambda = Expression.GreaterThanOrEqual(left, right);
break;
case "<=":
InnerLambda = Expression.LessThanOrEqual(left, right);
break;
case "!=":
InnerLambda = Expression.NotEqual(left, right);
break;
case "&&":
InnerLambda = Expression.And(left, right);
break;
case "||":
InnerLambda = Expression.Or(left, right);
break;
case "LIKE":
InnerLambda = Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), right);
break;
case "NOTLIKE":
InnerLambda = Expression.Not(Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), right));
break;
}
return InnerLambda;
}
public static Expression<Func<T, object>> PropExpr<T>(string PropName)
{
ParameterExpression paramExpr = Expression.Parameter(typeof(T));
var tempExpr = Extentions.NestedExprProp(paramExpr, PropName);
return Expression.Lambda<Func<T, object>>(Expression.Convert(Expression.Lambda(tempExpr, paramExpr).Body, typeof(object)), paramExpr);
}
public static IQueryOver<T, T> OrderBy<T>(this IQueryOver<T, T> Collection, string sidx, string sord)
{
return sord == "asc" ? Collection.OrderBy(NHibernate.Criterion.Projections.Property(sidx)).Asc : Collection.OrderBy(NHibernate.Criterion.Projections.Property(sidx)).Desc;
}
public static Expression<Func<T, TResult>> And<T, TResult>(this Expression<Func<T, TResult>> expr1, Expression<Func<T, TResult>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, TResult>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}