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.
Related
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);
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.
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
I want to Concat two expressions for the final expression
Expression<Func<T, string>>
So I have created expression belwo code works fine for only string types , If I get memberExpression as Int32 or DateTime throwing exception
Expression of type 'System.Int32' cannot be used for parameter of type 'System.String' of method 'System.String Concat(System.String, System.String)'
If I convert the expression as
var conversion = Expression.Convert(memberExpression, typeof (string));
getting No coercion operator is defined between types 'System.Int32' and 'System.String'.
Please help me to resolve
Code
MethodInfo bodyContactMethod = typeof (string).GetMethod("Concat",new[] {typeof (string), typeof (string)});
ParameterExpression parameter = Expression.Parameter(typeof (T));
body = Expression.Call(bodyContactMethod, cons, memberExpression);
return Expression.Lambda<Func<T, string>>(body, parameter);
Instead of trying to cast to string, you could try casting to object then calling ToString(), as though you were doing:
var converted = member.ToString();
As an Expression, it will look something like this:
var convertedExpression = Expression.Call(
Expression.Convert(memberExpression, typeof(object)),
typeof(object).GetMethod("ToString"));
It can be further simplified to:
var convertedExpression = Expression.Call(
memberExpression,
typeof(object).GetMethod("ToString"));
Rather than calling string.Concat(string, string), you could try calling string.Concat(object, object):
MethodInfo bodyContactMethod = typeof (string).GetMethod("Concat",
new[] { typeof(object), typeof(object) });
To expand on Richard Deeming's answer even though it's a little late.
Expression.Call(
typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) }),
Expression.Convert(cons, typeof(object)),
Expression.Convert(memberExpression, typeof(object))
);
That should work just fine while allowing the signature to stay as you have it.
(Code below has been updated and worked properly)
There is dynamic OrderBy sample from LinqPad. What I want to do is just simply apply 'Where' rather than 'OrderBy' for this sample. Here is my code:
IQueryable query =
from p in Purchases
//where p.Price > 100
select p;
string propToWhere = "Price";
ParameterExpression purchaseParam = Expression.Parameter (typeof (Purchase), "p");
MemberExpression member = Expression.PropertyOrField (purchaseParam, propToWhere);
Expression<Func<Purchase, bool>> lambda = p => p.Price < 100;
lambda.ToString().Dump ("lambda.ToString");
//Type[] exprArgTypes = { query.ElementType, lambda.Body.Type };
Type[] exprArgTypes = { query.ElementType };
MethodCallExpression methodCall =
Expression.Call (typeof (Queryable), "Where", exprArgTypes, query.Expression, lambda);
IQueryable q = query.Provider.CreateQuery (methodCall);
q.Dump();
q.Expression.ToString().Dump("q.Expression");
This code gets exception:
"InvalidOperationException: No method 'Where' on type 'System.Linq.Queryable' is compatible with the supplied arguments."
Any help is appraicated.
Cheers
Your lambda expression creation looks odd to me. You're adding another parameter for no obvious reason. You're also using Predicate<Purchase> instead of Func<Purchase, bool>. Try this:
LambdaExpression lambda = Expression.Lambda<Func<Purchase, bool>>(
Expression.GreaterThan(member, Expression.Constant(100)),
purchaseParam);
Use the lambda that Jon Skeet supplied. Perhaps he can also explain why ParameterExpression is so painful to use and requires using the same instance, instead of being able to be matched by name :)
Modify this line:
Type[] exprArgTypes = { query.ElementType };
exprArgTypes is type parameters to
IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate).
As you can see it only has one type parameter - TSource, which is Purchase. What you were doing, effectively, was calling Where method with two type parameters like below:
IQueryable<Purchase> Where<Purchase, bool>(this IQueryable<Purchase> source, Expression<Func<Purchase, bool>> predicate)
Once both of those fixes are in the expression runs with no problem.