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.
Related
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.
My objective is to create a subquery expression tree for a dynamic Full Text Search. In SQL it would be the equivalent of
SELECT *
FROM MV
WHERE MV.ID IN (SELECT ID
FROM MVF
WHERE title = "foo" OR Description = "foo")
So the basic idea is to create the FTS subquery, get the ids from that and use those for the In predicate. My issue is with the second part of that.
// Get subquery for FTS tables
ParameterExpression ftsParam = Expression.Parameter(typeof(MVF), "mvfts");
var wphrase = Expression.Constant("foo");
var methodInfo = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
var ftsID = Expression.Property(ftsParam, "ID");
var ftsTitle = Expression.Property(ftsParam, "Title");
var ftsDescrip = Expression.Property(ftsParam, "Description");
var texp = Expression.Call(ftsTitle, methodInfo, wphrase);
var dexp = Expression.Call(ftsDescrip, methodInfo, wphrase);
var ftsExp = Expression.Or(texp, dexp);
// Now get ids from the above fts resultset
// THE ASSIGNMENT BELOW THROWS
var selectExp = Expression.Call(typeof(IEnumerable<MVF>), "Select", new Type[]
{
typeof(long)
},
ftsExp,
Expression.Lambda<Func<MFV, long>>(
ftsID,
ftsParam
)
);
// Now set up MV table reference
ParameterExpression vParam = Expression.Parameter(typeof(MV), "mv");
var mvID = Expression.Property(vParam, "MVID");
var containsInfo = typeof(IEnumerable<long>).GetMethod("Contains", new Type[] { typeof(long) });
// Now combine expression to get those mvs with ids in the result set of fts query
var containsExp = Expression.Call(selectExp, containsInfo, mvID);
return Expression.Lambda<Func<MV, bool>>(containsExp, vParam);
Exception is:
No generic method 'Select' on type
'System.Collections.Generic.IEnumerable`1[MVF]' is compatible with the
supplied type arguments and arguments. No type arguments should be
provided if the method is non-generic.
Both methods needed by the expression in question are static generic extension methods (with the most important being static and generic) of the Enumerable class:
Enumerable.Select
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> selector
)
Enumerable.Contains
public static bool Contains<TSource>(
this IEnumerable<TSource> source,
TSource value
)
The most convenient way of "calling" such methods is the following Expression.Call method overload:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments
)
The Type type argument is the type of the class defining the method being called (typeof(Enumerable) in this case) and the Type[] typeArguments is the array with the types of the generic type arguments (empty for non generic methods, should be { typeof(TSource), typeof(TResult) } for Select and { typeof(TSource) } for Contains).
Applying it to your scenario:
var selectExp = Expression.Call(
typeof(Enumerable),
"Select",
new { typeof(MFV), typeof(long) },
ftsExp,
Expression.Lambda<Func<MFV, long>>(ftsID, ftsParam)
);
and
var containsExp = Expression.Call(
typeof(Enumerable),
"Contains",
new [] { typeof(long) },
selectExp,
mvID
);
typeof(IEnumerable<long>) does not define a select method. The Linq Select Methods are Extension Methods not visible directly via reflection. The class that defines them is Enumerable. However typeof(Enumerable) will not work either as the method is generic. You have to get the generic method from the class enumerable first and then use MethodInfo.MakeGenericMethod to create a method which takes a long parameter.
var method = typeof(Enumerable).GetMethods().First(m => m.Name == "Select" &&
m.GetParameters().Last().ParameterType.GetGenericArguments().Length == 2);
var genericSelectFromLongToLong = method.MakeGenericMethod(new Type[] {typeof(long), typeof(long)});
Note that it would probably be better to check the generic arguments instead of the argument count for a match.
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 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.
I am trying to apply a simple "Where" clause on a dynamically selected table. However, the table field the clause will be applied to is also dynamic and I can't figure out how to make that part work. Getting the dynamic table works correctly.
using (var context = new DBEntities())
{
var type = context.GetType();
var tableProperty = type.GetProperty("tableName");
var tableGet = tableProperty.GetMethod;
var tableContent = tableGet.Invoke(context, null);
var tableQuery = (IQueryable)tableContent;
var tableType = tableQuery.ElementType;
var pe = Expression.Parameter(tableType, "tableType");
var left = Expression.PropertyOrField(pe, "fieldName");
var right = Expression.Constant("fieldValue");
var predicateBody = Expression.Equal(left, right);
var whereCallExpression = Expression.Call(typeof(Queryable), "Where", new[] { tableType },
tableQuery.Expression, Expression.Lambda<Func<tableType, bool>>(predicateBody, pe));
IQueryable<string> results = tableQuery.Provider.CreateQuery<string>(whereCallExpression);
}
This block of code will not compile because of Expression.Lambda<Func<tableType, bool>>(predicateBody, pe). If I hard-code types for the Expression-related code, this sample runs and returns the expected results.
The code does not compile because tableType, a variable of type System.Type, cannot be used as a type parameter of a generic function.
However, you should be able to make this compile and run by replacing a call to generic Lambda<Func<...>> with a call to non-generic Lambda:
var whereCallExpression = Expression.Call(typeof(Queryable), "Where", new[] { tableType },
tableQuery.Expression, Expression.Lambda(predicateBody, pe));