I have a dynamic Linq query
public static IQueryable<T> WhereHelper<T>(this IQueryable<T> source, string property, string propertyValue) where T : class
{
//STEP 1: Verify the property is valid
var searchProperty = typeof(T).GetProperty(property);
//STEP 2: Create the OrderBy property selector
var parameter = Expression.Parameter(typeof(T), "o");
MemberExpression member = Expression.Property(parameter, property);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
ConstantExpression constant = Expression.Constant(propertyValue);
MethodCallExpression queryExpr = Expression.Call(member, method, constant);
ParameterExpression pe = Expression.Parameter(typeof(string), property);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<string, bool>>(queryExpr, new ParameterExpression[] { pe }));
return source.Provider.CreateQuery<T>(whereCallExpression);
}
This function generates an error which is the following:
No generic method 'Where' 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.
An idea on the error, thank you
You need to pass Func<T, bool> because the predicate for Where method is a function to test each element for a condition and type of input elements is T while the output is bool.
Finally, when creating the lambda expression, pass the same parameter used in queryExpr, otherwise it'd raise error variable not found. So instead of pe, it is parameter.
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(
queryExpr,
new ParameterExpression[] { parameter }
)
);
Related
My code is a slight revision from the sample here:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/how-to-use-expression-trees-to-build-dynamic-queries
I am writing an extension method that allows for performing unionon any property in the source/destination list and has the following signature
public static IEnumerable<TSource> UnionOn<TSource, TProperty>(
this IEnumerable<TSource> first,
Expression<Func<TSource, TProperty>> expression,
IEnumerable<TSource> second)
{
var finalList = new List<TSource>();
finalList.AddRange(first);
var queryableData = finalList.AsQueryable();
foreach (var item in second.ToList())
{
var propertyValue = expression.Compile().Invoke(item);
// x=>x.ExtendedPropertyId == 'guid_value'
var sourceObjectParam = Expression.Parameter(typeof(TSource), "x");
var propertyName = ((MemberExpression)expression.Body).Member.Name;
var left = Expression.Property(sourceObjectParam, propertyName);
var right = Expression.Constant(propertyValue);
var predicateBody = Expression.Equal(left, right);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Enumerable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<TSource, Boolean>>(predicateBody, new ParameterExpression[] { sourceObjectParam }));
// **** this line causes runtime error *****
IQueryable<TSource> resultsQuery = queryableData.Provider.CreateQuery<TSource>(whereCallExpression);
if (resultsQuery.ToList().Any())
finalList.Add(item);
}
return finalList;
}
The exception reads on generating the resultsQuery method:
System.ArgumentException: 'Argument expression is not valid
However, when I look at the debugview of the generated expression in whereCallExpression it looks fine to me:
.Call System.Linq.Enumerable.Where(
.Constant<System.Linq.EnumerableQuery`1[A]>(System.Collections.Generic.List`1[A]),
.Lambda #Lambda1<System.Func`2[A,System.Boolean]>)
.Lambda #Lambda1<System.Func`2[A,System.Boolean]>(A $x) {
$x.ExtendedPropertyId == .Constant<System.Guid>(fadd6b4e-8d97-404c-bcf1-
c5ebd02230a6)
}
Any help will be much appreciated.
There are many inefficiencies in this code. And no real benefit of using expressions when working with IEnumerables - Func<..> parameters similar to standard LINQ Enumerable methods would be enough.
But to answer your concrete question. The exception is because you are calling CreateQuery with expression returning IEnumerable<TSource> (due to Enumerable.Where method call) while it expects (requires) IQueryable (or IQueryable<TSource>) type expression.
The fix is simple - change typeof(Enumerable) to typeof(Queryable) in your Expression.Call (the other parameters are unchanged) and the problem will be gone.
Also note that when you have compile time type TSource and IQueryable<TSource> and Expression<Func<TSource, bool>> variables, there is no need to compose Where call and CreateQuery - you could simple use Queryable.Where extension method directly, e.g. given
var predicate = Expression.Lambda<Func<TSource, bool>>(predicateBody, sourceObjectParam);
the
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
predicate);
IQueryable<TSource> resultsQuery =
queryableData.Provider.CreateQuery<TSource>(whereCallExpression);
can be replaced with
IQueryable<TSource> resultsQuery = queryableData.Where(predicate);
thus avoiding errors like this.
I'm following this excellent example: Convert Linq to Sql Expression to Expression Tree
In my case I'm trying to build an expression tree where the type to be filtered is only known at run time, and is expressed as a string. In the above example the type Region is already known and typed directly:
ParameterExpression pe = Expression.Parameter(typeof(Region), "region");
In my application I've been able to rewrite this as:
ParameterExpression pe = Expression.Parameter(Type.GetType("mystring"), "listData");
My stumbling block is this from the example:
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { query.ElementType },
query.Expression,
Expression.Lambda<Func<Region, bool>>(e3, new ParameterExpression[] { pe }));
var results = query.Provider.CreateQuery<Region>(whereCallExpression);
In these two lines Region is directly typed. Is there a way of dynamically using a string "Region" to achieve the same thing?
Sure, but you'll have to understand the implications. The type name Region is the compile time type. With it, you can generate a strongly typed query. However, since you don't have the type at compile time, you can still generate a query, but it won't be strongly typed.
You can generate an lambda expression of unknown compile time type by using the non-generic overloads. Likewise with the CreateQuery() method.
Here's two versions of the same query which checks if some property value matches a given value. One is generic and the other is not.
The generic version implicitly takes the type from the type of the query.
public IQueryable<TSource> PropertyEqualsValue<TSource>(IQueryable<TSource> query,
string propertyName, object value)
{
var param = Expression.Parameter(typeof(TSource));
var body = Expression.Equal(
Expression.Property(param, propertyName),
Expression.Constant(value)
);
var expr = Expression.Call(
typeof(Queryable),
"Where",
new[] { typeof(TSource) },
query.Expression,
Expression.Lambda<Func<TSource, bool>>(body, param)
);
return query.Provider.CreateQuery<TSource>(expr);
}
var query = PropertyEqualsValue(SomeTable, "SomeColumn", "SomeValue");
While the other the type is taken from the provided typeName. Note that when the query is created, we cannot provide the type, since we don't know what the type is at compile time.
public IQueryable PropertyEqualsValue(IQueryable query,
Type type, string propertyName, object value)
{
var param = Expression.Parameter(type);
var body = Expression.Equal(
Expression.Property(param, propertyName),
Expression.Constant(value)
);
var expr = Expression.Call(
typeof(Queryable),
"Where",
new[] { type },
query.Expression,
Expression.Lambda(body, param)
);
return query.Provider.CreateQuery(expr);
}
var type = Type.GetType("Some.Type.Name");
var query = PropertyEqualsValue(SomeTable, type, "SomeColumn", "SomeValue");
I have a method that builds an expression tree, based on the Type of the object that is passed to the method. Once the tree is built, I want to convert it and return it with the return type as is shown below.
public static Expression<Func<object, bool>> BuildExpression(Type type, ...)
{
// build the expression...
ParameterExpression param = Expression.Parameter(type, "m");
Expression expression = null;
// simplified version of building the expression tree
MemberExpression member = Expression.Property(param, filter.Property);
ConstantExpression constant = Expression.Constant(filter.Value);
expression = Expression.Equal(member, constant);
// ...
// IT FAILS ON THIS LINE!!!
return Expression.Lambda<Func<object, bool>>(expression, param);
}
I've looked at a few conversion answers, but to no avail. Any advice?
Here is your code with modifications as described in my previous comment.
1) Your function returns expression that describes function with single argument. And this argument is of type Object. So you should use Object type when creating parameter "m" expression.
2) Before accessing the property parameter should be cast back to desired type. See Expression.Convert.
public static Expression<Func<object, bool>> BuildExpression(Type type, ...)
{
// build the expression...
ParameterExpression param = Expression.Parameter(typeof(Object), "m");
Expression expression = null;
UnaryExpression convert = Expression.Convert(param, type);
// simplified version of building the expression tree
MemberExpression member = Expression.Property(convert, filter.Property);
ConstantExpression constant = Expression.Constant(filter.Value);
expression = Expression.Equal(member, constant);
// ...
return Expression.Lambda<Func<object, bool>>(expression, param);
}
I am trying to create a dynamic where clause using Linq Expressions for an IQueryable data source. I can't get the TryParse function to work in one of the Expressions. Here is what I am trying to do:
IQueryable<trial_global> globalTrials = _trialsRepository.GlobalDataFiltered(filterId).AsQueryable();
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Static;
MethodInfo tryParseMethod = typeof(double).GetMethod("TryParse", bindingFlags, null, new Type[] { typeof(string), typeof(double).MakeByRefType() }, null);
Expression tempN = Expression.Parameter(typeof(double), "tempN");
Expression left = Expression.Call(tryParseMethod, new[] { metricReference, tempN });
Expression right = Expression.Constant(true);
Expression predicateBody = Expression.Equal(left, right);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { globalTrials.ElementType },
globalTrials.Expression,
Expression.Lambda<Func<trial_global, bool>>(predicateBody, new ParameterExpression[] { pe })
);
var results = globalTrials.Provider.CreateQuery<trial_global>(whereCallExpression);
Everything works fine until results gets assigned where I get the following error:
variable 'tempN' of type 'System.Double' referenced from scope '', but it is not defined
What am I missing here? I suspect it has to do with the 2nd parameter in the double.TryParse function being an out parameter.
UPDATE:
I got around the issue by creating a static function which does the TryParse and calling this static function from the Expression:
public static bool IsStringNumeric(string checkStr)
{
double num = 0;
return double.TryParse(checkStr, out num);
}
public IQueryable<trial_global> GetTrials(IQueryable<trial_global> globalTrials, Metric metric)
{
ParameterExpression pe = Expression.Parameter(typeof(trial_global), "trial_global");
MemberExpression metricReference = Expression.Property(pe, metric.metric_name);
Expression predicateBody = Expression.Call(typeof(GlobalTrialsRepository).GetMethod("IsStringNumeric", new Type[] { typeof(string) }), metricReference);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { globalTrials.ElementType },
globalTrials.Expression,
Expression.Lambda<Func<trial_global, bool>>(predicateBody, new ParameterExpression[] { pe })
);
return globalTrials.Provider.CreateQuery<trial_global>(whereCallExpression);
}
Is this approach ok? Does anyone see any disadvantages of doing it like this?
Tryparse use out parameter for the check. Create an extension method with the tryparse and then call the extension method from linq
Linq queries are deferred until a ToXXXX() is called on it. You can, therefore, define the Where statement as a Linq method chain and then just call .ToList or whatever to get the value.
Starting with the following:
string propertyName = TextBoxPropertyToGet.Text;
ParameterExpression arg = Expression.Parameter(typeof(Item), "x");
Expression expr = Expression.Property(arg, propertyName);
LambdaExpression lambda = Expression.Lambda(expr, arg);
Expression<Func<Item, string>> expression = (Expression<Func<Item, string>>)lambda;
What I was hoping to do:
ParameterExpression arg = Expression.Parameter(typeof(Item), "x");
PropertyInfo pi = typeof(Item).GetProperty(propertyName);
Type pt = pi.PropertyType;
Expression expr = Expression.Property(arg, pi);
LambdaExpression lambda = Expression.Lambda(expr, arg);
Expression<Func<Item, pt>> expression = (Expression<Func<Item, pt>>)lambda;
but I get the error:
The type or namespace name 'pt' could not be found (are you missing a using directive or an assembly reference?) On the last line.
I don't want to use:
if(pt == typeof(string))
Expression<Func<Item, string>>....
else if(pt==typeof(decimal))
Expression<Func<Item, decimal>>....
....
....
I have also tried:
var expression = Expression.Lambda(
Expression.Convert(Expression.Property(
arg, propertyName),
pt),
arg);
AND
var lambdaMethodParamType = typeof(Func<,>).MakeGenericType(typeof(Item), pt);
var expression = typeof(Expression).GetMethods().First(x => x.Name == "Lambda" && x.IsGenericMethod).MakeGenericMethod(lambdaMethodParamType);
but then I get:
The type arguments for method 'System.Linq.Enumerable.Select<TSource,TResult>(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,int,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
basically telling me to do what I don't want to do.
I have tried (as proposed by Servy)
string propertyName = TextBoxPropertyToGet.Text;;
ParameterExpression arg = Expression.Parameter(typeof(Item), "x");
Expression expr = Expression.Property(arg, propertyName);
LambdaExpression lambda = Expression.Lambda(expr, arg);
var x = Queryable.Select(MyContext.Items,(dynamic)lambda);
but this returns empty, and when trying to call var x = Queryable.Select(MyContext.Items,(dynamic)lambda).Distinct(); I get the error 'System.Data.Entity.Infrastructure.DbQuery<string>' does not contain a definition for 'Distinct'
You need to call Select using reflection, rather than calling it normally, if the generic arguments are not known at compile time. This means getting the Type of Queryable, getting the Select method, choosing the right overload, etc.
The alternative is to use dynamic, which does pretty much the same thing, but handles generating pretty much of that dynamic typing for you:
LambdaExpression lambda = Expression.Lambda(expr, arg);
dynamic query = Queryable.Select(data, (dynamic)lambda);