I want to implement generic pager and filter View Model for my project and I'm stuck on getting OrderBy method using reflection. Here is what I've tried, but keep getting null for methodInfo. It seems I'm passing the wrong Type[] arguments to the GetMethod() Method, but I can't get it right.
protected virtual Expression<Func<T, IComparable>> GetOrderByExpression()
{
var type = typeof(T);
var property = type.GetProperty("DataSetName");
var parameter = Expression.Parameter(type, "x");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
var methodInfo = typeof(Enumerable).GetMethod("OrderBy", new Type[] { orderByExp.Body.Type });
var predicateBody = Expression.Call(propertyAccess, methodInfo, orderByExp);
var expression = Expression.Lambda<Func<T, IComparable>>(predicateBody, parameter);
return expression;
}
Then Enumerable.OrderBy extension method is a static method so you have to use an overload to GetMethod where you can specify BindingFlags (BindingFlags.Static | BindingFlags.Public instead of the default BindingFlags.Instance | BindingFlags.Public).
You also have to specify two parameters to the method - syntactically it looks like there is only one parameter but because it is an extension method there is a second parameter which you have to specify.
This is the method you want to get via reflection:
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector
)
You need the two parameter types:
var sourceType = typeof(IEnumerable<>).MakeGenericType(type);
var keySelectorType = orderByExp.Compile().GetType();
It seems that the only way to get a specific overload of a method with generic parameters you have to perform a search. Fortunately, there are only two overloads of Enumerable.OrderBy and the one you want is the one with two parameters:
var genericMethodInfo = typeof(Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.First(mi => mi.Name == "OrderBy" && mi.GetParameters().Length == 2);
var methodInfo = genericMethodInfo.MakeGenericMethod(sourceType, keySelectorType);
This returns the desired MethodInfo, however you will have to modify the remaining two lines of code because this method is a static method and you have to specify a null instance in Expression.Call.
Related
Here is what I am trying to do. I have done a few simple expressions, but this one is a little too much for me right now.
public static Expression<Func<IQueryable<TEntityType>, IOrderedQueryable<TEntityType>>> SortMeDynamically<TEntityType>(bool isAsc, string propertyname)
{
var param = Expression.Parameter(typeof(TEntityType), "x");
var prop = Expression.PropertyOrField(param, propertyname);
var sortLambda = Expression.Lambda(prop, param);
string sortOrder = isAsc ? "OrderBy" : "OrderByDescending";
var selector = Call(
typeof(Queryable),
sortOrder,
new[] { prop.Type},
sortLambda);
var lambda = Lambda<Func<IQueryable<TEntityType>, IOrderedQueryable<TEntityType>>>(selector, param);
return lambda;
}
The error I get is the following.
System.InvalidOperationException: 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
The closest solution I found was the one below.
LINQ to Entities OrderBy Expression Tree
Calling the various .OrderBy linq methods dynamically is quite a pain.
The most difficult part of this process is locating the MethodInfo of the four relevant Queryable.OrderBy methods, with the correct generic constraints. There are a number of ways to achieve this.
You could pull the method out of a template Expression<Func<...>>, as in your linked answer;
Expression<Func<IOrderedEnumerable<TEntityType>>> sortMethod =
(() => query.OrderBy<TEntityType, object>(k => null));
var methodCallExpression = (sortMethod.Body as MethodCallExpression);
var method = methodCallExpression.Method.GetGenericMethodDefinition();
You could use Type.GetMethods and filter the results. Though you'd have to worry about future runtime changes breaking this approach.
var method = typeof(Queryable).GetMethods()
.Where(m => m.IsGenericMethod
&& m.Name == nameof(Queryable.OrderBy)
&& m.GetParameters().Length == 2)
.Single();
In both cases you'd then need to call .MakeGenericMethod to supply the correct generic parameters.
var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
Or you could create a delegate and pull the method from there. Again, getting the generic constraints correct is a bit fiddly. But can be easier with a helper method. Which is similar to how the linq runtime locates this MethodInfo.
public MethodInfo GetOrderFunc<T, V>(Func<IQueryable<T>, Expression<Func<T, V>>, IOrderedQueryable<T>> func)
=> func.Method;
var genericSortMethod = GetOrderFunc<TEntityType, V>(Queryable.OrderBy);
If you don't know the argument value type, you could call that method via reflection.
Now you can either invoke the method;
var orderedQuery = (IOrderedQueryable<TEntityType>)genericSortMethod.Invoke(null, new object[] { query, sortLambda });
Or by reading the source code, recreate what those methods actually do.
var expression = query.Expression;
expression = Expression.Call(
typeof(Queryable),
genericSortMethod,
new Type[] { typeof(TEntityType), prop.Type },
expression,
Expression.Quote(sortLambda));
var orderedQuery = (IOrderedQueryable<T>)query.Provider.CreateQuery<T>(expression);
No matter how you approach this, you need to take an IQueryable<TEntity> query parameter, and return an IOrderedQueryable<TEntity>. Or just stop at creating the Expression<Func<>>.
The other, other option is to move all the generic mucking around into a helper method. Then invoke that method via reflection. Obtaining the generic MethodInfo using one of the same approaches explained above.
public IOrderedQueryable<T> Order<T, V>(this IQueryable<T> query, Expression<Func<T, V>> key, bool then, bool desc)
=> (desc)
? (then ? ((IOrderedQueryable<T>)query).ThenByDescending(key) : query.OrderByDescending(key))
: (then ? ((IOrderedQueryable<T>)query).ThenBy(key) : query.OrderBy(key));
In a C# project, I want to create an extension that would take a list of property names and create a dynamic select query.
I found magic Gist that seems to be doing exactly that. However, var lambda = ... keeps throwing the following error
Lambda type parameter must be derived from System.Delegate
Here is the code
public static IQueryable<dynamic> ToDynamic<T>(this IQueryable<T> query, IEnumerable<String> fields)
{
var pocoType = typeof(T);
var itemParam = Expression.Parameter(pocoType, "x");
var members = fields.Select(f => Expression.PropertyOrField(itemParam, f));
var addMethod = typeof(IDictionary<string, object>).GetMethod(
"Add", new Type[] { typeof(string), typeof(object) });
var elementInits = members.Select(m => Expression.ElementInit(addMethod, Expression.Constant(m.Member.Name), Expression.Convert(m, typeof(object))));
var expando = Expression.New(typeof(ExpandoObject));
var lambda = Expression.Lambda<Expression<Func<T, dynamic>>>(Expression.ListInit(expando, elementInits), itemParam);
return query.Select(lambda.Compile());
}
How can I correct this error?
In Expression.Lambda<T> expression T is considered lambda type parameter that must be derived from System.Delegate. So you just need to remove wrapping Expression and also there is no need to call Compile at the end since IQueryable expect to receive Expression<Func<T, F>> and not a Func<T, F> which is for IEnumerable
//..
var lambda = Expression.Lambda<Func<T, dynamic>>(Expression.ListInit(expando, elementInits), itemParam);
return query.Select(lambda);
Related To:
Create a Lambda Expression With 3 conditions
Convert Contains To Expression Tree
In the following of my previous question I faced with this query that I want to write Expression Tree version:
List<byte?> lst = new List<byte?>{1,2};
from a in myTbl
where a.Age = 20 && lst.Contains(a.Status)
select a
I write this code:
List<byte?> lst = new List<byte?>{1,2};
var param = Expression.Parameter(typeof(T), "o");
var body = Expression.AndAlso(
Expression.Equal(
Expression.PropertyOrField(param, "Age"),
Expression.Constant(20)
),
Expression.Call(Expression.PropertyOrField(param, "Status"),
"Contains",
Type.EmptyTypes,
Expression.Constant(lst)));
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return lambda;
and I get the error:
"No method 'Contains' exists on type 'System.Nullable`1[System.Byte]'."
Please help me to find the problem.
Thanks
The difference from Convert Contains To Expression Tree is that there we were calling a string instance Contains method, while here we need to call a static generic method Enumerable.Contains:
public static bool Contains<TSource>(
this IEnumerable<TSource> source,
TSource value
)
It can be achieved by using another Expression.Call overload:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments
)
like this:
// Enumerable.Contains<byte?>(lst, a.Status)
var containsCall = Expression.Call(
typeof(Enumerable), // type
"Contains", // method
new Type[] { typeof(byte?) }, // generic type arguments (TSource)
Expression.Constant(lst), // arguments (source)
Expression.PropertyOrField(param, "Status") // arguments (value)
);
The problem is that you have switched two arguments to Expression.Call, your code is trying to create the nonsensical expression o.Status.Contains(lst).
You need to switch the two arguments around:
Expression.Call(Expression.Constant(lst),
"Contains",
Type.EmptyTypes,
Expression.PropertyOrField(param, "Status"))
This is assuming that the LINQ provider you're using understands List<T>.Contains(). If you need Enumerable.Contains(), then have a look at Ivan Stoev's answer.
This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
How to use a string to create a EF order by expression?
(1 answer)
Closed 6 years ago.
I want to be able to get an OrderBy query working with a lambda expression so that I get a SQL query with the TOP(n) key word (big performance boost).
I am able to do this if I specifiy ...
PaginatedList = query.OrderBy(x => x.QuoteID).Skip(() => skipValue).Take(() => pageSize)
But because I want the orderBy field to be dynamic through a UI selection of a name I want to do something like this:
var propertyInfo = typeof(Data.Quote).GetProperty(sortName);
Expression<Func<Data.Quote, object>> orderField = x => propertyInfo.GetValue(x, null);
PaginatedList = query.OrderBy(orderField).Skip(() => skipValue).Take(() => pageSize)
This gives me the error:
"LINQ to Entities does not recognize the method 'System.Object
GetValue(System.Object)' method, and this method cannot be translated
into a store expression."
I tried this that's not of type Expression<Func<T, object>>
var propertyInfo = typeof(Data.Quote).GetProperty(sortName);
Func<Data.Quote, object> orderField = x => propertyInfo.GetValue(x, null);
PaginatedList = query.OrderBy(x => orderField).Skip(() => skipValue).Take(() => pageSize)
And I get this error:
"Unable to create a constant value of type [...]. Only primitive types
or enumeration types are supported in this context"
I'm sure there is a way to achieve this but at the moment not sure how.
Here is how to achieve what you want:
var propertyInfo = typeof(Data.Quote).GetProperty(sortName);
ParameterExpression parameter = Expression.Parameter(typeof(T), "s");
MemberExpression property = Expression.Property(parameter, propertyInfo);
LambdaExpression sort = Expression.Lambda(property, parameter);
MethodCallExpression call = Expression.Call(
typeof(Queryable),
"OrderBy",
new[] {typeof(T), property.Type},
Query.Expression,
Expression.Quote(sort));
var orderedQuery = (IOrderedQueryable<T>)Query.Provider.CreateQuery<T>(call);
PaginatedList = orderedQuery.Skip(skipValue).Take(pageSize);
Instead of that you need to create an expression to select that property.From this source:
public static class Utility
{
//makes expression for specific prop
public static Expression<Func<TSource, object>> GetExpression<TSource>(string propertyName)
{
var param = Expression.Parameter(typeof(TSource), "x");
Expression conversion = Expression.Convert(Expression.Property
(param, propertyName), typeof(object)); //important to use the Expression.Convert
return Expression.Lambda<Func<TSource, object>>(conversion, param);
}
public static IOrderedQueryable<TSource>
OrderBy<TSource>(this IQueryable<TSource> source, string propertyName)
{
return source.OrderBy(GetExpression<TSource>(propertyName));
}
}
Then you can order by as I show below:
var result=Query.OrderBy(sortName)...;
The value being copied in to propertyInfo is just an Object, as that is the type returned by GetProperty(). Hover over var and you will confirm this.
The GetValue method doesn't exist for an Object, so you need to cast it to the right type before making the call to GetValue.
I'm trying to write dynamic code that do some aggregations Average, Sum, Max, etc.
That's the code im executing :
PropertyInfo sortProperty = typeof(T).GetProperty("PropertyName");
ParameterExpression parameter = Expression.Parameter(typeof(T), "p");
MemberExpression propertyAccess = Expression.MakeMemberAccess(parameter, sortProperty);
LambdaExpression orderByExp = Expression.Lambda(propertyAccess, parameter);
var exp = Expression.Lambda<Func<T, int>>(propertyAccess, parameter);
var call = Expression.Call(typeof(Enumerable), "Average", new[] { typeof(IEnumerable<T>) , typeof(Func<T, int>) }, parameter);
and I always get that exception:
No generic method 'Average' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
Let's look at this line. Here you're calling Call
var call = Expression.Call(typeof(Enumerable), "Average", new[] { typeof(IEnumerable<T>) , typeof(Func<T, int>) }, parameter);
The third parameter is "An array of Type objects that specify the type parameters of the generic method.". You're passing the types IEnumerable<T> and Func<T, int>, but Average takes only a single type parameter (TSource).
The forth parameter is "An array of Expression objects that represent the arguments to the method.". You're passing an expression representing a T, but Average expects an IEnumerable<TSource> and a Func<TSource, decimal> (or whatever overload you want to call, I'll just use the decimal one as an example).
I don't know what your final goal is using this code, but it probably should look like:
PropertyInfo sortProperty = typeof(T).GetProperty("Prop");
ParameterExpression parameter = Expression.Parameter(typeof(T), "p");
MemberExpression propertyAccess = Expression.MakeMemberAccess(parameter, sortProperty);
// parameter for the source collection
ParameterExpression source = Expression.Parameter(typeof(IEnumerable<T>), "source");
var exp = Expression.Lambda<Func<T, decimal>>(propertyAccess, parameter);
var call = Expression.Call(typeof(Enumerable), "Average", new[] {typeof(T)}, source, exp);
Here's a small example using this code (you'll get the idea):
// assuming a class called T with a decimal property called Prop
// because I'm a lazy and terrible person
var method = Expression.Lambda<Func<IEnumerable<T>, decimal>>(call, source).Compile();
var result = method(new List<T> {new T { Prop=10}, new T { Prop=20}});
// result is now 15