I have the following code block I am using to perform some dynamic filtering on a generic IQueryable list
private static MethodInfo miTL = typeof(String).GetMethod("ToLower", Type.EmptyTypes);
public static IQueryable<T> Where<T>(IQueryable<T> source, string member, object value)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (member == null)
{
throw new ArgumentNullException("member");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value is string && !string.IsNullOrWhiteSpace(value.ToString()))
{
//If the type is string, force lowercase equals comparison for both sides.
value = value.ToString().ToLower();
var parameter = Expression.Parameter(typeof(T), "item");
var parameterProperty = Expression.Property(parameter, member);
//ToLower dynamic expression
var dynamicExpression = Expression.Call(parameterProperty, miTL);
var constantValue = Expression.Constant(value);
var equalsExpression = Expression.Equal(dynamicExpression, constantValue);
var lambdaExpression = Expression.Lambda<Func<T, bool>>(equalsExpression, parameter);
return source.Where(lambdaExpression);
}
else
{
var item = Expression.Parameter(typeof(T), "item");
var prop = Expression.Property(item, member);
var soap = Expression.Constant(value);
var equal = Expression.Equal(prop, soap);
var lambda = Expression.Lambda<Func<T, bool>>(equal, item);
return source.Where(lambda);
}
}
This all works fine - apart from the possibility of my source containing null values, which then returns a null reference exception.
It directly translates to (when the field is "Counterparty" :- {item => (item.Counterparty.ToLower() == "name of counterparty")}
What I actually need in Lambda expression form is :-
{item => !string.IsNullEmptyOrWhitespace(item.Counterparty) && (item.Counterparty.ToLower() == "name of counterparty")}
Any ideas how to achieve this dynamically?
--REVIEWED--
Here is the whole replacement code, using a much nicer string.Equals check
public static IQueryable<T> Where<T>(IQueryable<T> source, string member, object value)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (member == null)
{
throw new ArgumentNullException("member");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value is string && !string.IsNullOrWhiteSpace(value.ToString()))
{
var parameter = Expression.Parameter(typeof(T), "item");
var parameterProperty = Expression.Property(parameter, member);
var body =
Expression.AndAlso(
Expression.Not(
Expression.Call(typeof(string), "IsNullOrEmpty", null, parameterProperty)
),
Expression.Call(typeof(string), "Equals", null,
parameterProperty, Expression.Constant(value),
Expression.Constant(System.StringComparison.InvariantCultureIgnoreCase))
);
var body2 = Expression.Call(typeof(string), "Equals", null,
parameterProperty, Expression.Constant(value),
Expression.Constant(System.StringComparison.InvariantCultureIgnoreCase));
var lambdaExpression = Expression.Lambda<Func<T, bool>>(body, parameter);
return source.Where(lambdaExpression);
}
else
{
var item = Expression.Parameter(typeof(T), "item");
var prop = Expression.Property(item, member);
var soap = Expression.Constant(value);
var equal = Expression.Equal(prop, soap);
var lambda = Expression.Lambda<Func<T, bool>>(equal, item);
return source.Where(lambda);
}
}
A literal translation would be something like:
var body =
Expression.AndAlso(
Expression.Not(
Expression.Call(typeof(string), "IsNullOrWhiteSpace", null,
parameterProperty)
),
Expression.Equal(
Expression.Call(parameterProperty, "ToLower", null),
Expression.Constant("name of counterparty")
)
);
However, you would do well to look at the various string.Equals overloads. For example:
var body = Expression.Call(typeof(string), "Equals", null,
parameterProperty, Expression.Constant("name of counterparty"),
Expression.Constant(System.StringComparison.InvariantCultureIgnoreCase));
Related
I'm using Visual Studio 2019. I'm doing filter with the below function. My search result is always sensitive for a string value. For employee name, it would be case-insensitive search, so user can enter upper or lowercase searchValue. My list is converted ToList() to AsQueryable()
list = list.Where(AppendFilters<CustomerApiResponse>(data.filter.filters));
Please see the below function:
protected ExpressionStarter<T> AppendFilters<T>(List<Filter2> filters)
{
var predicate = PredicateBuilder.New<T>();
foreach (var item in filters)
{
predicate = predicate.And(GetExpression<T>(item.field, item.value, item.#operator));
}
return predicate;
}
Here's what I have:
private Expression<Func<T, bool>> GetExpression<T>(string propName, object val, string op)
{
ParameterExpression argParam = Expression.Parameter(typeof(T));
var nameProperty = Expression.Property(argParam, propName);
var opp = EnumExtensions.ParseEnum<KendoOperators>(op).GetDescription();
MethodInfo method = typeof(string).GetMethod(opp, new[] { typeof(string) });
Expression e1;
if (nameProperty.Type == typeof(int))
{
var propertyType = ((PropertyInfo)nameProperty.Member).PropertyType;
var converter = TypeDescriptor.GetConverter(propertyType);
if (!converter.CanConvertFrom(typeof(string)))
throw new NotSupportedException();
var propertyValue = converter.ConvertFrom(val);
var constant = Expression.Constant(propertyValue);
var valueExpression = Expression.Convert(constant, propertyType);
if (op == "neq")
{
e1 = Expression.NotEqual(nameProperty, valueExpression);
}
else
{
e1 = Expression.Equal(nameProperty, valueExpression);
}
}
else
{
var constant = Expression.Constant(val);
e1 = Expression.Call(nameProperty, method, constant);
}
return Expression.Lambda<Func<T, bool>>(e1, argParam);
}
How do I change the above code to make filter to be case-insensitive for searches any string value?
Thank you!
I want to populate the itemsource of a ComboBox with items from my List, depending on which property from T is selected.
The statement should be like:
foreach property which is a string,
select the values of the property, make distinct.
public Dictionary<string, List<string>> CreateSuggestionsLists<T>(List<T> data)
{
var queryableData = data.AsQueryable();
var paramExp = Expression.Parameter(typeof(T), "left");
foreach (var pi in typeof(T).GetProperties().Where(p => p.PropertyType == typeof(string)))
{
var callExpr = Expression.MakeMemberAccess(paramExp, pi);
var lambdaExpr = Expression.Lambda(callExpr) ;
// From here on it goes wrong!!!
var comleteExpr = lambdaExpr as Expression<Func<T, bool>>;
var compiledExpr = comleteExpr.Compile();
var res = data.Select(compiledExpr).Distinct().ToList();
// add to results ...
}
return null;
}
The problem seems to be the casting from the lambda expression to prepare for compilation.
Thank you for your help.
First of all you need to provide paramExp to lambda. Secondly there is generic version of Lamda method which is just easier to use. Finally, you don't need to compile expression when you use IQueryable. You created queryableData variable and didn't use it.
Here is code:
public Dictionary<string, List<string>> CreateSuggestionsLists<T>(List<T> data)
{
var queryableData = data.AsQueryable();
var paramExp = Expression.Parameter(typeof(T), "left");
foreach (var pi in typeof(T).GetProperties().Where(p => p.PropertyType == typeof(string)))
{
var callExpr = Expression.MakeMemberAccess(paramExp, pi);
var lambdaExpr = Expression.Lambda<Func<T, bool>>(callExpr, paramExp);
var res = queryableData.Select(lambdaExpr).Distinct().ToList();
// add to results ...
}
return null;
}
I think you should check if the casting result is not null :
public Dictionary<string, List<string>> CreateSuggestionsLists<T>(List<T> data)
{
IQueryable<T> queryableData = data.AsQueryable();
ParameterExpression paramExp = Expression.Parameter(typeof(T), "left");
foreach (PropertyInfo pi in typeof(T).GetProperties().Where(p => p.PropertyType == typeof(string)))
{
MemberExpression callExpr = Expression.MakeMemberAccess(paramExp, pi);
LambdaExpression lambdaExpr = Expression.Lambda(callExpr);
// From here on it goes wrong!!!
if (!(lambdaExpr is Expression<Func<T, bool>> comleteExpr)) continue;
Func<T, bool> compiledExpr = comleteExpr.Compile();
List<bool> res = data.Select(compiledExpr).Distinct().ToList();
// add to results ...
}
return null;
}
I am creating lambda expression for Iqueryable to get value from a collection but I want to convert that value to other datatype like int or decimal. So as I cannot use c# casting with Iqueryable so I have created user defined scalar function in sql and trying to access that in expression but it throws exception that the 'methodname' cannot be converted to sql expression.
public class Context
{
[DbFunction("dbo", "ConvertToDouble")]
public int? ConvertToDouble(string value)
{
var sql = $"set #result = dbo.[ConvertToDouble]('{value}')";
var output = new SqlParameter { ParameterName = #"result", DbType = DbType.Int32, Size = 16, Direction = ParameterDirection.Output };
var result = Database.ExecuteSqlCommand(sql, output);
return output.Value as int?;
}
}
private static Expression<Func<TSource, TDataType>> CreateLamdaExpression<TSource, TDataType>(string fieldName)
{
var parameterExpression = Expression.Parameter(typeof(TSource));
var collectionParameter = Expression.Property(parameterExpression, "CustomFieldValues");
var childType = collectionParameter.Type.GetGenericArguments()[0];
var propertyParameter = Expression.Parameter(childType, childType.Name);
var left = Expression.Property(propertyParameter, "Name");
var right = Expression.Constant(fieldName);
var innerLambda = Expression.Equal(left, right);
var innerFunction = Expression.Lambda(innerLambda, propertyParameter);
var method = typeof(Enumerable).GetMethods().Where(m => m.Name == "FirstOrDefault" && m.GetParameters().Length == 2).FirstOrDefault().MakeGenericMethod(typeof(CustomFieldValue));
var outerLambda = Expression.Call(method, Expression.Property(parameterExpression, collectionParameter.Member as System.Reflection.PropertyInfo), innerFunction);
var propertyGetter = Expression.Property(outerLambda, "Value");
if (typeof(TDataType) != typeof(object))
{
/var changeTypeCall = Expression.Call(Expression.Constant(Context), Context.GetType().GetMethod("ConvertToDouble", BindingFlags.Public | BindingFlags.Instance),
propertyGetter
);
Expression convert = Expression.Convert(changeTypeCall,
typeof(TDataType));
return Expression.Lambda<Func<TSource, TDataType>>(convert, new ParameterExpression[] { parameterExpression });
}
var result = Expression.Lambda<Func<TSource, TDataType>>(propertyGetter, new ParameterExpression[] { parameterExpression });
return result;
}
I have create where clause for equal successfully using dynamic linq expression using following code-
Expression<Func<T, bool>> whereClause = null;
var parameter = Expression.Parameter(typeof(T), "c");
Expression filterExpression = null;
List<Filter> filters = new List<Filter>(){new Filter(){PropertyName= "id" , Value="2" , Operation = "Equal"}}
var DistinctItems = filters.GroupBy(x => x.PropertyName).Select(y => y.First());
foreach (var filterItem in DistinctItems)
{
var member = Expression.Property(parameter, filterItem.PropertyName);
var propertyType = ((PropertyInfo)member.Member).PropertyType;
var converter = TypeDescriptor.GetConverter(propertyType);
if (!converter.CanConvertFrom(typeof(string)))
throw new NotSupportedException();
var propertyValue = converter.ConvertFromInvariantString(filterItem.Value.ToString());
var constant = Expression.Constant(propertyValue);
var valueExpression = Expression.Convert(constant, propertyType);
Expression equality = null;
switch (filterItem.Operation)
{
case "Equal":
equality = Expression.Equal(member, valueExpression);
break;
case "LessThan":
equality = Expression.LessThan(member, valueExpression);
break;
//case "In": how to do for IN forexample where id in
//(1,2)
// equality = Expression.(member, valueExpression);
// break;
}
if (filterExpression == null)
{
filterExpression = equality;
}
else
{
filterExpression = Expression.And(filterExpression, equality);
}
}
if (filterExpression != null)
{
whereClause = Expression.Lambda<Func<T, bool>>(filterExpression, parameter);
}
return whereClause;
How to do it for IN clause , for example - Where Id IN (1,2)
I am new to linq c# , Following is my function
public static IQueryable<T> BuildWhereExpression<T>(this IQueryable<T> query, SearchAttributes searchModel)
{
string FilterField = searchModel.FilterField;
string FilterOperator = searchModel.FilterOperator;
string FilterValue = searchModel.FilterValue;
ParameterExpression ParamExp = Expression.Parameter(typeof(T), GlobalConstants.SearchExpressionName);
Expression InitialExp;
LambdaExpression FinalExp;
switch (FilterOperator)
{
case GlobalConstants.IsEqualTo:
if (FilterValue == "")
InitialExp = Expression.Call(Expression.PropertyOrField(ParamExp, FilterField), typeof(string).GetMethod("Contains"), Expression.Constant(FilterValue));
else
InitialExp = Expression.Equal(Expression.PropertyOrField(ParamExp, FilterField), Expression.Constant(FilterValue));
break;
case GlobalConstants.Contains:
{ // This is what i havd tried till now
//var Column = Expression.PropertyOrField(ParamExp, FilterField);
//var isNull = Expression.Equal(Column, Expression.Constant(null));
//Expression left = Expression.Call(Column, typeof(string).GetMethod("ToString", System.Type.EmptyTypes));
//Expression left = Expression.Call(pe)
}
InitialExp = Expression.Call(Expression.PropertyOrField(ParamExp, FilterField), typeof(string).GetMethod("Contains"), Expression.Constant(FilterValue));
break;
case GlobalConstants.StartsWith:
InitialExp = Expression.Call(Expression.PropertyOrField(ParamExp, FilterField), typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), Expression.Constant(FilterValue));
break;
default:
InitialExp = Expression.Constant(true);
break;
}
FinalExp = Expression.Lambda<Func<T, bool>>(InitialExp, new ParameterExpression[] { ParamExp });
MethodCallExpression result = Expression.Call(typeof(Queryable), "Where", new Type[] { query.ElementType }, query.Expression, Expression.Quote(FinalExp));
return query.Provider.CreateQuery<T>(result);
}
The above code adds a condition for contains in a column dynamically.
Contains does not works for column containing null values.
How can i implement following logic
If table.ColumnValue is Null replace the column null with empty string then compair with the value in FilterValue
EDIT:
I mean how can i implement query as
coalesce(table.column,string.empty) == FilterValue
Please help me over this.
Thanks in advance.
The expression you are looking for is something like:
Expression<Func<T, bool>> exp = x => (x.FilterField ?? string.Empty).Contains(FilterValue);
that can be obtained with
var coalesce = Expression.Coalesce(
Expression.PropertyOrField(ParamExp, FilterField),
Expression.Constant(string.Empty))
so
InitialExp = Expression.Call(coalesce, typeof(string).GetMethod("Contains"), Expression.Constant(FilterValue));
Note that, considering future-proofing, I would always explicitly tell the .NET the parameters of the method I'm looking for:
typeof(string).GetMethod("Contains", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null);
because you can't know if, in .NET ∞.0, they'll finally add an overload that supports a StringComparison :-)