Dynamically building an expression tree - c#

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");

Related

Dynamic Linq Where with Lambda Expression generates Error

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 }
)
);

Dynamically ordering by string

I've created an extension on IQueryable as I would like to order by nullable datetimes first then order by the datetime itself using just the string of the property i.e "activeTo". I've created the code below:
public static IQueryable<T> Sort<T>(this IQueryable<T> source, string sortBy)
{
//create the expression tree that represents the generic parameter to the predicate
var param = Expression.Parameter(typeof(T), "p");
//create an expression tree that represents the expression p=>p.SortField.HasValue
var prop = Expression.Property(param, sortBy);
var target = Expression.Constant(null, prop.Type);
var bin = Expression.Equal(prop, Expression.Convert(target, prop.Type));
var exp = Expression.Lambda(bin, param);
string method = "OrderBy";
Type[] types = new Type[] { source.ElementType, exp.Body.Type };
var orderByCallExpression = Expression.Call(typeof(Queryable), method, types, source.Expression, exp);
//now do the ThenBy bit,sending in the above expression to the Expression.Call
exp = Expression.Lambda(prop, param);
types = new Type[] { source.ElementType, exp.Body.Type };
method = "ThenBy";
var ThenByCallExpression = Expression.Call(typeof(Queryable), method, types, orderByCallExpression, exp);
return source.Provider.CreateQuery<T>(ThenByCallExpression);
}
This extension is called by:
query.Sort("activeTo");
Which then gives the below the response:
{
"title": "test 5",
"activeFrom": "2019-06-08T21:26:50.2833333",
"activeTo": "2019-06-08T21:26:50.2833333",
},
{
"title": "test 2",
"activeFrom": "2019-06-08T21:28:45.65",
"activeTo": null,
}
I'd expect the record with activeTo as null to be first however, this isn't the case.
Does anyone know what I'm doing wrong?
From the comments the goal seems to be to dynamically generate an expression which sorts null values to the front.
The current code produces the following expression OrderBy(p => p.activeTo == null).ThenBy(p => p.activeTo == null). This has two flaws:
It sorts null values to the front as the bools sort order is false, true (as their ordinal values are 0 and 1, respectively). Therefore a comparison to null first collects the false cases, and then the truecases.
The ThenBy repeats the OrderBy, but was actually intended to emit ThenBy(p => p.ActiveTo).
The first can be solved by either using Expression.NotEqual instead of Expression.Equal for p => p != p.activeTo or by using OrderByDescending instead of OrderBy.
In total the code should be:
public static IQueryable<T> Sort<T>(IQueryable<T> source, string sortBy)
{
//create the expression tree that represents the generic parameter to the predicate
var param = Expression.Parameter(typeof(T), "p");
//create an expression tree that represents the expression p=>p.SortField.HasValue
var prop = Expression.Property(param, sortBy);
var target = Expression.Constant(null, prop.Type);
// NotEqual, to sort nulls before not-nulls
var bin = Expression.NotEqual(prop, Expression.Convert(target, prop.Type));
var exp = Expression.Lambda(bin, param);
// OrderBy with the null comparison expression
string method = nameof(Queryable.OrderBy);
Type[] types = new Type[] { source.ElementType, exp.Body.Type };
var orderByCallExpression = Expression.Call(typeof(Queryable), method, types, source.Expression, exp);
// ThenBy with the property expression
exp = Expression.Lambda(prop, param);
types = new Type[] { source.ElementType, exp.Body.Type };
method = nameof(Queryable.ThenBy);
var ThenByCallExpression = Expression.Call(typeof(Queryable), method, types, orderByCallExpression, exp);
return source.Provider.CreateQuery<T>(ThenByCallExpression);
}
This yields the following expression:
OrderBy(p => p.activeTo != null).ThenBy(p => p.activeTo).
Remarks: It should be noted that usually OrderBy(p => p.activeTo) would already sort null values to the front, as this is the default sort order for strings, nullables, and so on. However, this behavior could be overwritten depending by the specific type and depend on the query source. Therefore, I left it like the OP has.

How to create an anonymous object as a LINQ Expression

I would like to create the following lambda expression using an Expression Tree in C#:
var result = dataList.GroupBy(x => new { x.Prop1, x.Prop2 })
How do I make the anonymous type with two properties as a LINQ Expression (lambdaExp)?
This is what I got so far:
IQueryable<GraphData> queryableData = graphDataList.AsQueryable();
ParameterExpression pe = Expression.Parameter(typeof(GraphData), "x");
Expression prop1 = Expression.PropertyOrField(pe, "Prop1");
Expression prop2 = Expression.PropertyOrField(pe, "Prop2");
var lambdaExp = Expression.Lambda<Func<GraphData, object>>( new { prop1, prop2 } , pe); //doesn't compile
MethodCallExpression groupByCallExpression = Expression.Call(
typeof(Queryable),
"GroupBy",
new Type[] { typeof(GraphData), typeof(object) },
queryableData.Expression,
lambdaExp);
IQueryable<GraphData> result = queryableData.Provider.CreateQuery<GraphData>(groupByCallExpression);
When you're writing linq queries and anonymous objects, the compiler is hiding a lot of the magic that it's doing for you. Specifically with anonymous objects, it is also creating new types for you. Unless that type exists somewhere, you will need to create the type manually and use that type in its place.
You can cheat a bit and have the compiler create an object with that type and save a reference to that type. With that, you can generate the necessary expressions to instantiate that object.
One thing to note is that the anonymous objects created for you will have constructors with the parameters in definition order so you just need to invoke that constructor.
var keyType = new { Prop1=default(string), Prop2=default(string) }.GetType();
var ctor = keyType.GetConstructor(new Type[] { typeof(string), typeof(string) });
var param = Expression.Parameter(typeof(GraphData), "x");
var keySelector = Expression.Lambda(
Expression.New(ctor,
Expression.PropertyOrField(param, "Prop1"), // corresponds to Prop1
Expression.PropertyOrField(param, "Prop2") // corresponds to Prop2
),
param
); // returns non-generic LambdaExpression
Do keep in mind that we are dealing with types not known at compile time, so your key selector expression will not have a compile time type.

Unable to get double.TryParse to work in Linq Expression Tree

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.

Dynamically set out type in Expression Func

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);

Categories

Resources