I have a extension method which creates a lambda predicate expression, which I use to filter data when the value is not null.
It works fine for cases like
query.FilterBy(obj => obj.MyProp, value);
but it fails in cases like
query.FilterBy(obj => obj.MyObject.MyProp, value);
In this case the expression for MyProp property is accessed on obj, which of course doesn't have the property.
I'm not familiar with the expression tree and could not figure out how to modify the extension method to access properties of sub objects. Does anyone have an idea how I could do it?
The extension method:
public static IQueryable<TSource> FilterBy<TSource, TProp>(this IQueryable<TSource> source,
Expression<Func<TSource, TProp>> property,
TProp value)
{
if (value != null)
{
var parameter = Expression.Parameter(typeof(TSource));
var memberExpression = property.Body as MemberExpression ??
((UnaryExpression)property.Body).Operand as MemberExpression;
if (memberExpression is null)
{
throw new InvalidOperationException("Please provide a valid property expression.");
}
var propertyName = memberExpression.Member.Name;
var body = Expression.Equal(
Expression.Property(parameter, propertyName),
Expression.Constant(value));
var predicate = Expression.Lambda<Func<TSource, bool>>(body, parameter);
source = source.Where(predicate);
}
return source;
}
You could simply use epxression passed like parameter property of FilterBy() function for creating new filtering expression instead of trying to create expression from scratch. It will works with "sub-properties".
public static IQueryable<TSource> FilterBy<TSource, TProp>(this IQueryable<TSource> source,
Expression<Func<TSource, TProp>> property,
TProp value)
{
if (value != null)
{
var expression = Expression.Equal(property.Body, Expression.Constant(value));
var predicate = Expression.Lambda<Func<TSource, bool>>(expression, property.Parameters[0]);
source = source.Where(predicate);
return source;
}
return source;
}
Related
I have a collection which is of type IQueryable, I need to sort this based on some dynamic sort fields. Sort fields are inside a list.
I write the following method to do this.
public List<T> Order<T>(IQueryable<T> source, List<string> propertyNames)
{
if(propertyNames != null && propertyNames.Count > 0)
{
var param = Expression.Parameter(typeof(T), string.Empty);
var property = Expression.PropertyOrField(param, propertyNames[0]);
var sort = Expression.Lambda(property, param);
MethodCallExpression orderByCall = Expression.Call(typeof(Queryable),"OrderBy",new[] { property.Type },Expression.Quote(sort));
if(propertyNames.Count > 1)
{
foreach(var item in propertyNames)
{
param = Expression.Parameter(typeof(T), string.Empty);
property = Expression.PropertyOrField(param, item);
sort = Expression.Lambda(property, param);
orderByCall = Expression.Call(
typeof(Queryable),
"ThenBy", new[] { typeof(T), property.Type },
orderByCall,
Expression.Quote(sort));
}
}
var results = (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(orderByCall);
if(results != null)
return results.ToList();
}
return null;
}
when I executed MethodCallExpression orderByCall = Expression.Call(typeof(Queryable),"OrderBy",new[] { property.Type },Expression.Quote(sort)); I got some exception
No generic method 'OrderBy' on type 'System.Linq.Queryable' is
compatible with the supplied type arguments and arguments. No type
arguments should be provided if the method is non-generic.
Sorry, I don't have a direct solution for your error.
Here is an alternative ("kept it simple") approach to dynamically order your data.
1) Add these extension methods somewhere in your project
public static IOrderedQueryable<TSource> OrderBy<TSource, TProperty>(this IQueryable<TSource> source
, Expression<Func<TSource, TProperty>> expression, bool descending)
{
return !descending ? source.OrderBy(expression) : source.OrderByDescending(expression);
}
public static IOrderedQueryable<TSource> ThenBy<TSource, TProperty>(this IOrderedQueryable<TSource> source
, Expression<Func<TSource, TProperty>> expression, bool descending)
{
return !descending ? source.ThenBy(expression) : source.ThenByDescending(expression);
}
2) Now you can just loop your list of property names and apply the OrderBy / ThenBy on your IQueryable.
Other idea: you could adapt your method so it accepts expressions instead of property name strings.
I use the following code to convert order by expressions so nullable columns can be ordered by as well.
protected virtual Expression<Func<T, object>> GetSorting(string ordering)
{
Expression<Func<T, object>> expression = default(Expression<Func<T, object>>);
IEnumerable<Order> sortObjects = string.IsNullOrEmpty(ordering) ? null : JsonConvert.DeserializeObject<IEnumerable<Order>>(ordering);
if (sortObjects != null)
{
foreach (Order sortObject in sortObjects)
{
Expression<Func<T, object>> currentExpression = this.GetExpression(sortObject.Property);
expression = this.CombineExpressions(expression, currentExpression);
}
}
return expression;
}
private Expression<Func<T, object>> GetExpression(string propertyName)
{
Type type = typeof(T);
ParameterExpression parameter = Expression.Parameter(type, "x");
MemberExpression propertyReference = Expression.Property(parameter, propertyName);
Expression conversion = Expression.Convert(propertyReference, typeof(object));
Expression<Func<T, object>> currentExpression = Expression.Lambda<Func<T, object>>(conversion, new[] { parameter });
return currentExpression;
}
private Expression<Func<T, object>> CombineExpressions(Expression<Func<T, object>> expression, Expression<Func<T, object>> currentExpression)
{
if (expression == default(Expression<Func<T, object>>))
{
expression = currentExpression;
}
else
{
// Combine the two expressions' body together
BinaryExpression body = Expression.AndAlso(expression.Body, currentExpression.Body);
ParameterExpression[] parameters = new ParameterExpression[1] { Expression.Parameter(typeof(T), expression.Parameters.First().Name) };
// Convert the BinaryExpression to the requested type
Expression<Func<T, object>> lambda = Expression.Lambda<Func<T, object>>(body, parameters);
expression = lambda;
}
return expression;
}
This code works perfectly for all non-navigation properties but it seems as if the navigation properties aren't queried anymore. I use a Select expression to load the navigation properties, like so:
protected override Expression<Func<Resource, ResourceViewModel>> Selector
{
get
{
return (x) => new ResourceViewModel()
{
ResourceId = x.ResourceId,
DisplayName = x.DisplayName,
ResourceType = x.ResourceType != null ? x.ResourceType.Name : string.Empty,
}
}
}
If I don't have anything to order by, the navigation properties are loaded. But as soon as there is anything to order by, the navigation property is null. If I would skip the ternary operation and straightly go to the ResourceType.Name property, I'd get an exception telling me the lambda_method has thrown a NullReference exception.
I know ordering navigation properties doesn't work as such but that's not the problem. Ordering by 'regular' properties causes the problem.
Any thoughts on this?
Turns out the issue wasn't as complex as I thought. The problem was that I created the wrong expression tree. You can nest the expression so you can navigate to the property of a property.
The solution below should explain this (this isn't how I actually solved the issue but it should make it clear):
private Expression<Func<T, object>> GetExpression(string parentClass, string propertyName)
{
Type type = typeof(T);
ParameterExpression parameter = Expression.Parameter(type, "x");
// Get parent class expression
// Will result in (x) => x.MyNavigationPropertyWhichIsAClass
MemberExpression propertyReference1 = Expression.Property(parameter, parentclass);
// Navigate to the property of the navigation property class
// Will result in (x) => x.MyNavigationPropertyWhichIsAClass.MyPropertyIWantToSort
MemberExpression propertyReference2 = Expression.Property(propertyRefernce1, propertyName);
Expression conversion = Expression.Convert(propertyReference2, typeof(object));
Expression<Func<T, object>> currentExpression = Expression.Lambda<Func<T, object>>(conversion, new[] { parameter });
return currentExpression;
}
So I'm attempting to build a semi complication Search expression, but I'm stuck trying to create a basic one. The expressions being used for getValueExpression look something like:
x => x.PropertyA != null ? x.PropertyA.ToShortDateString() : "" //nullable datetime
x => x.PropertyB //string property
x => x.PropertyC != null x.PropertyC.ToString() : "" //nullable int
Here is my function code, it currently errors when getValueExpression being of type Func that can't be compared to a string, which makes perfect sense and I understand why that is, but I'm having trouble figuring out how to make an expression that gets the value of getValueExpression to compare to the value being searched for. Any help or leads in the right direction would be greatly appreciated.
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, string>> getValueExpression, string searchOption, string searchValue)
{
var searchValueExpression = Expression.Constant(searchValue);
var comparisonExpression = Expression.Equal(getValueExpression, searchValueExpression);
var lambdaExpression = Expression.Lambda<Func<TSource, bool>>(comparisonExpression);
return source.Where(lambdaExpression);
}
I've attempted similar things like this, but have met failure with incorrect arguments amount exception:
var getValueExpressionValue = Expression.Call(getValueExpression.Compile().Method, parameterValueExpression);
Here is a method that will let you compose expressions; that is to say you can use the output of one expression as the input of another, creating a new expression taking the input that the first takes and the output that the second takes:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Which uses the following method to replace one expression with another:
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
This lets you write:
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source,
Expression<Func<TSource, string>> getValueExpression,
string searchOption,
string searchValue)
{
var predicate = getValueExpression.Compose(value => value == searchValue);
return source.Where(predicate);
}
Here is how you can do it :
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, string>> getValueExpression, string searchOption, string searchValue)
{
// const searchValue
var searchValueExpression = Expression.Constant(searchValue);
// parameter x
var parameterExpression = Expression.Parameter(typeof(TSource));
// func(x)
var parameterGetValueExpression = Expression.Invoke(getValueExpression, parameterExpression);
// func(x) == searchValue
var comparisonExpression = Expression.Equal(parameterGetValueExpression, searchValueExpression);
// x => func(x) == searchValue
var lambdaExpression = Expression.Lambda<Func<TSource, bool>>(comparisonExpression, parameterExpression);
return source.Where(lambdaExpression);
}
I have created a predicate that looks like:
p.Name.Contains("Saw")
and I have the following code that works:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue)
{
PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName);
// ListOfProducts.Where(p => p.Contains(propertyValue))
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo);
MethodInfo methodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string));
// Predicate Body - p.Name.Contains("Saw")
Expression call = Expression.Call(memberExpression, methodInfo, constantExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe);
return lambda;
}
But I want to change the predicate to:
p.Name.ToLower().Contains("Saw")
and I'm coming up blank. I know that I have to add something somewhere where the MethodInfo is defined.
Does anyone have a suggestion?
Rather than constructing the entire expression manually just because one tiny little piece is dynamic, you can use a regular lambda to define all of the contents that are in fact static, and then just replace the little bit that isn't.
Specifically, the general strategy you can use is to have a lambda with a parameter representing your little dynamic bit, and then using it in a regular lambda, and then replace all instances of that parameter with your dynamically constructed expression:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(
string propertyName, string propertyValue)
{
Expression<Func<string, bool>> e = s => s.ToLower().Contains(propertyValue);
var parameter = Expression.Parameter(typeof(T));
var property = Expression.PropertyOrField(parameter, propertyName);
var body = e.Body.Replace(e.Parameters[0], property);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
This not only simplifies your original code, but makes other changes to this static code as easy as editing any regular old C# code, rather than requiring all of the expression manipulation, and the complexity (and loss of static typing) that comes along with it.
This solution uses the following method to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Another approach, allowing things to be even more high level, is to write a Compose method that lets you compose expressions easily. Conceptually we'll have two lambdas, and we want to create a lambda that represents invoking one and passing its result to the other, and then it returning the result:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
This uses more or less the same strategy that we used above, but it generalizes it instead of special casing it to your specific expressions.
We then have one remaining helper method to make before putting the pieces together; creating a method that represents accessing a property as defined by the string name of the property:
public static Expression<Func<T, string>> MemberSelector<T>(string propertyName)
{
var param = Expression.Parameter(typeof(T));
var body = Expression.PropertyOrField(param, propertyName);
return Expression.Lambda<Func<T, string>>(body, param);
}
Using these two helper methods (that aren't dependent on any particular situation), we can now construct the lambda that we want without any custom built expression manipulation:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(
string propertyName, string propertyValue)
{
return MemberSelector<T>(propertyName)
.Compose(prop => prop.ToLower().Contains(propertyValue));
}
You have to get the expression for the ToLowermethod, then use it in the Contains expression
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue)
{
PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName);
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo);
//ToLower expression
MethodInfo toLowerMethodInfo = typeof (string).GetMethod("ToLower", new Type[]{});
Expression toLowerCall = Expression.Call(memberExpression, toLowerMethodInfo);
MethodInfo containsMethodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string));
// Pass ToLowerCall to
Expression call = Expression.Call(toLowerCall, containsMethodInfo, constantExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe);
return lambda;
}
With respect to Andre's answer, which is essentially what I came up after Servy's initial comment, I wanted to post what I came up with:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue)
{
PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName);
// ListOfProducts.Where(p => p.Contains(propertyValue))
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo);
// Thanks to Servy's suggestion
Expression toLowerExpression = Expression.Call(memberExpression, typeof(string).GetMethod("ToLower", Type.EmptyTypes));
MethodInfo methodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string));
// Predicate Body - p.Name.Contains("Saw")
Expression call = Expression.Call(toLowerExpression, methodInfo, constantExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe);
return lambda;
}
I'm writing a query framework, and trying to make it as generic as possible.
Let's say I have a query based on person, and I want the ability to filter on both the first and last names, and in both cases I want to be able to use filter conditions like StartsWith, 'EndsWith, Contains, Equals.
So now I have a method:
private Expression<Func<Person, bool>> FirstNameFilter(Comparator comparator, string compareValue) {
switch (comparator) {
case Comparator.Equal:
return p => p.FirstName == compareValue;
case Comparator.Contains:
return p => p.FirstName.Contains(compareValue);
case Comparator.StartsWith:
return p => p.FirstName.StartsWith(compareValue);
// etc.
}
}
Now, I also want to be able to build the same filter for LastName. Seems silly and wasteful to copy and paste the whole thing over again, just replacing p.FirstName with p.LastName. I also have a bunch of other string fields that I want to filter on, and I really don't want to have to rewrite this whole method for each one!
Is there some way to abstract this, maybe using LinqKit, so that I can come out with a more generic method with the following approximate signature:
Expression<Func<Person, bool>> GetFilter(Expression<Func<Person, string>> stringExpression, Comparator comparator, string compareValue) {}
such that I could, inside FirstNameFilter, invoke it like so:
return GetFilter(p => p.FirstName, comparator, compareValue);
Something like that (untested, but you have the idea) should help you to build the needed expressions :
public static class LinqQueries
{
private static MethodInfo toLowerMethod = typeof(String).GetMethod("ToLower", Type.EmptyTypes);
private static MethodInfo startsWithMethod= typeof(String).GetMethod("StartsWith", new Type[] { typeof(String) });
private static MethodInfo containsMethod = typeof(String).GetMethod("Contains", new Type[] { typeof(String) });
private static MethodInfo endsWithMethod= typeof(String).GetMethod("EndsWith", new Type[] { typeof(String) });
public static Expression<Func<T, bool>> GetFilter(Expression<Func<T, string>> expression, Comparator comparator, string compareValue) {
ParameterExpression parameterExpression = null;
var memberExpression = GetMemberExpression(expression.Body, out parameterExpression);
Expression constExp = Expression.Constant(compareValue);
switch (comparator) {
case Comparator.Contains:
memberExpression = Expression.Call(memberExpression, containsMethod,constExp);
break;
case Comparator.StartsWith:
memberExpression = Expression.Call(memberExpression, startsWithMethod, constExp);
break;
//etc.
default :
memberExpression = Expression.Equal(memberExpression, constExp);
break;
}
return Expression.Lambda<Func<T, bool>>(memberExpression, new[]{parameterExpression});
}
private static Expression GetMemberExpression(Expression expression, out ParameterExpression parameterExpression)
{
parameterExpression = null;
if (expression is MemberExpression)
{
var memberExpression = expression as MemberExpression;
while (!(memberExpression.Expression is ParameterExpression))
memberExpression = memberExpression.Expression as MemberExpression;
parameterExpression = memberExpression.Expression as ParameterExpression;
return expression as MemberExpression;
}
if (expression is MethodCallExpression)
{
var methodCallExpression = expression as MethodCallExpression;
parameterExpression = methodCallExpression.Object as ParameterExpression;
return methodCallExpression;
}
return null;
}
}