I'm trying to build a LINQ expression, to filter values from a int property:
protected IQueryable<T> Some(IEnumerable<int> ids)
{
var parameter = Expression.Parameter(typeof(T), "x");
// "Col_id" (int property)
var property = Expression.Property(parameter, "Col_id");
MethodInfo method = typeof(Enumerable).
GetMethods().
Where(x => x.Name == "Contains").
Single(x => x.GetParameters().Length == 2).
MakeGenericMethod(typeof(T));
// ids = { 2, 4, 8 } etc...
var value = Expression.Constant(ids, typeof(IEnumerable<int>));
var containsMethod = Expression.Call(method, property, value); // exception
var aDelegate = Expression.Lambda<Func<T, bool>>(containsMethod, parameter);
table = myDataContext.GetTable<T>();
return table.AsQueryable().Where(aDelegate);
}
I'm trying to get something like: (x => ids.Contains(x.Col_id)), but an exception is thrown:
Expression of type 'System.Int32' cannot be used for type parameter
'System.Collections.Generic.IEnumerable'1[T] from 'Boolean
Contains[T](System.Collections.Generic.IEnumerable'1[T], T)' method
It looks to me like you've just got the arguments the wrong way round.
This:
Expression.Call(method, property, value)
means that you're calling:
Enumerable.Contains(x.Col_id, ids)
whereas you want
Enumerable.Contains(ids, x.Col_id)
So just try:
var containsMethod = Expression.Call(method, value, property);
EDIT: Next, you're building the wrong Contains type argument, I think. I suspect you want:
MethodInfo method = typeof(Enumerable).
GetMethods().
Where(x => x.Name == "Contains").
Single(x => x.GetParameters().Length == 2).
MakeGenericMethod(typeof(int));
After all, you want to call Enumerable.Contains<int>, not Enumerable.Contains<SomeType>.
Related
I need to dynamically execute the Include and Where method in it to build a query in EF. From the input parameters there is only the name of the field for Include and the condition in the form of a string.
What I want without dynamics looks like:
using System.Linq.Dynamic.Core;
contextEF.Table1.Include(x => x.Field1.Where("c => c.Id == 500")).ToList();
Here is my extreme attempt at getting this code to work dynamically:
var nameField = "Field1";
var queryWhere = "c => c.Id == 500";
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, typeof(T).GetProperty(nameField));
var typeItem = typeof(T).GetProperty(nameField).PropertyType.GetGenericArguments().First();
var lambda = DynamicExpressionParser.ParseLambda(ParsingConfig.Default, typeItem, typeof(bool), queryWhere);
var expressionWhere = Expression.Call(typeof(Queryable), "Where", new Type[] { typeItem }, lambda, property);
var expressionInclude = Expression.Lambda<Func<T, object>>(expressionWhere, parameter);
return queryable.Include(expressionInclude);
At the moment, I get errors with a call to the Where method and I can't move further:
System.InvalidOperationException: 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.
It is clear from the error that it cannot find the Where method, where the parameter is string. But I don't understand how the program can prove that there is such a method =)
I managed to solve the problem, it all boiled down to the fact that I did not correctly specify the types for the .Include method, because there I have .Where, then the return type will be IEnumerable, and not ICollection as I expected.
var nameField = "Field1";
var queryWhere = "c => c.Id == 500";
var queryLambda = $"x => x.{nameField}.Where({queryWhere}).Take(20)";
Type typeEnumerable = typeof(IEnumerable<>).MakeGenericType(TProperty);
LambdaExpression lambdaInclude = DynamicExpressionParser.ParseLambda(typeof(T), typeEnumerable, queryLambda);
var includeQ =
Expression.Call(
type: typeof(Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions),
methodName: "Include",
typeArguments: new Type[] { typeof(T), typeEnumerable },
arguments: new[] {
queryable.Expression,
Expression.Quote(lambdaInclude)
}
);
queryable = queryable.Provider.CreateQuery<T>(includeQ);
I have database column
SelectListId int null
And I have complicated filter on web page, which I am trying to transfer to LinqToSql in order to get filtered data from database.
I have several expressions which works, but one I struggle with.
I would like to call something like this x => SelectedIdsByUser.Contains(x.SelectListId)
So I have a function which returns predicate
// this function works for 'SelectListId int not null' columns
public static Expression<Func<T, bool>> SelectListContainsPredicate<T>(string columnName, List<int> searchValues)
{
var type = typeof(T);
ParameterExpression parameter = Expression.Parameter(type, "x");
ConstantExpression constant = Expression.Constant(true);
// When column is invalid, return true
PropertyInfo property = type.GetProperties().FirstOrDefault(p => p.Name == columnName);
if (property == null || searchValues.Count == 0)
{
return Expression.Lambda<Func<T, bool>>(constant, parameter);
}
// Define expression :
// x => SearchValues.Contains(x.Column)
MemberExpression member = Expression.Property(parameter, property);
MethodInfo method = typeof(List<int>).GetMethod("Contains");
constant = Expression.Constant(searchValues);
// Here it throws :
// System.ArgumentException: Expression of type 'System.Nullable`1[System.Int32]'
// cannot be used for parameter of type 'System.Int32' of method 'Boolean Contains(Int32)'
// Because: column is int? and List<int>.Contains(int?) doesn't work.
Expression expression = Expression.Call(constant, method, member);
return Expression.Lambda<Func<T, bool>>(expression, parameter);
}
but I am getting an error, because SelectListId is Nullable<int> but Contains method has only int parameter. What can I do here, any Idea ?
System.ArgumentException: Expression of type
'System.Nullable`1[System.Int32]' cannot be used for parameter of
type 'System.Int32' of method 'Boolean Contains(Int32)'
use
x => ( x.SelectListId != null ) ? SelectedIdsByUser.Contains( (int)(x.SelectListId) ) : 0
and replace 0 by intendent value.
I found solution here
https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression.convert?view=netframework-4.8
there is Expression.Convert method, I used :-)
Expression memberAsInt = Expression.Convert(member, typeof(Int32));
Expression expression = Expression.Call(constant, method, memberAsInt);
return Expression.Lambda<Func<T, bool>>(expression, parameter);
I am trying to write a LINQ query to orderBy a dynamic property given by a string value.
Here is what my original code was:
Expression<Func<T, dynamic>> orderBy = i => i.GetType().GetProperty("PropertyName").GetValue(null);
When I tried to run this orderBy I got the following exception:
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 am trying to work around this by creating an expression tree that will give me the same result. The code should be able to return any type depending on the parameter but I'm having trouble with the return type. If I don't convert the value I get a different error saying w Nullable DateTime can't be converted to Object. Here is the code that I have so far:
ParameterExpression pe = Expression.Parameter(typeof(T), "s");
Expression<Func<T, dynamic>> orderByExpression = Expression.Lambda<Func<T, dynamic>>(Expression.Convert(Expression.Property(pe, "PropertyName"), typeof(object)), pe);
and my new exception:
Unable to cast the type
'System.Nullable`1[[System.DateTime]]' to type
'System.Object'. LINQ to Entities only supports casting EDM primitive
or enumeration types.
How would I write this expression tree to do return a dynamic type? Also is there a better way I should be doing this in LINQ?
There are no template arguments for Expression<Func<T, TT>> which will allow you to represent both reference types and value types. Of course, you can build the expressions, but you must interact with them via reflection.
This will properly sort a collection:
IOrderedEnumerable<TEntityType> SortMeDynamically<TEntityType>(IEnumerable<TEntityType> query, string propertyname)
{
var param = Expression.Parameter(typeof(TEntityType), "s");
var prop = Expression.PropertyOrField(param, propertyname);
var sortLambda = Expression.Lambda(prop, param);
Expression<Func<IOrderedEnumerable<TEntityType>>> sortMethod = (() => query.OrderBy<TEntityType, object>(k => null));
var methodCallExpression = (sortMethod.Body as MethodCallExpression);
if (methodCallExpression == null)
throw new Exception("Oops");
var method = methodCallExpression.Method.GetGenericMethodDefinition();
var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
var orderedQuery = (IOrderedEnumerable<TEntityType>)genericSortMethod.Invoke(query, new object[] { query, sortLambda.Compile() });
return orderedQuery;
}
Or if you want it on an IQueryable (if you're using EF, for example)
IOrderedQueryable<TEntityType> SortMeDynamically<TEntityType>(IQueryable<TEntityType> query, string propertyname)
{
var param = Expression.Parameter(typeof(TEntityType), "s");
var prop = Expression.PropertyOrField(param, propertyname);
var sortLambda = Expression.Lambda(prop, param);
Expression<Func<IOrderedQueryable<TEntityType>>> sortMethod = (() => query.OrderBy<TEntityType, object>(k => null));
var methodCallExpression = (sortMethod.Body as MethodCallExpression);
if (methodCallExpression == null)
throw new Exception("Oops");
var method = methodCallExpression.Method.GetGenericMethodDefinition();
var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
var orderedQuery = (IOrderedQueryable<TEntityType>)genericSortMethod.Invoke(query, new object[] { query, sortLambda });
return orderedQuery;
}
I'm creating a method that receives a Queryable<T> source, a string with a property name/path (could be a deep property for example "TrParent.DataTypes" to achieve this x => x.TrParent.DataTypes) and Enumerable<int> which holds the values I need to intersect.
Basically I come from the need to create the following query dynamically (I mean <DT_Det_Tr> and TrParent.DataTypes being know only at runtime, in the example DT_Det_Tr is not a type it is a class):
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
Please keep in mind that the preceding query is just an example of what I need to achieve dynamically, what I really need is an expression tree that creates a predicate like the one shown above but using a dynamic type and with the deep navigation property specified within a string.
So, I'm using this function to create the expression for the deep property:
private static LambdaExpression CreateDelegateExpression<T>(out Type resultingtype, string property, string parameterName = "x")
{
var type = typeof(T);
ParameterExpression param = Expression.Parameter(type, parameterName);
Expression expr = param;
foreach (string prop in property.Split('.'))
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
resultingtype = type;
return lambda;
}
And here is what I have so far for my function:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
//List of ids
var _value = Expression.Constant(value);
//Get delegate expression to the deep property and it's inner type
Type type = null;
var lambda = CreateDelegateExpression<T>(out type, property, "x");
var enumtype = type.GetGenericArguments()[0];
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
MethodInfo innermethod = typeof(Queryable).GetMethods().Where(x => x.Name == "Select").First();
//Error on next line...
var selectCall = Expression.Call(typeof(Queryable),
"Select",
new Type[] { enumtype, typeof(long) },
lambda,
propExp);
//TODO: Add rest of logic and actually filter the source
return source;
}
In the var selectCall = line I'm getting error:
No generic method 'Select' 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.
I've read a lot here on SO and other sites but I can't get past this part, I feel I'm going to bump into more trouble when I get to the .Intersect(List<int>).Any() part so any help on that also would be grand, thanks.
After a lot of thought, investigation and attempts I came up with a solution.
First, I made a simpler version of my goal query (the static example I used in my question), so instead of:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
I made this:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Any(y => _vals.Contains(y.Id))
);
Which is a lot easier to translate to expressions (or at least it was for me) because it omits the Select call.
I got rid of the method I was using to create the deep navigation property expression and streamlined it in my Intersect function, this was because it was doing some work I don't really need here plus I needed access to some of the variables I use inside it, then I made this:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
var type = typeof(T);
var _value = Expression.Constant(value); //List of ids
//Declare parameter for outer lambda
ParameterExpression param = Expression.Parameter(type, "x");
//Outer Lambda
Expression expr = param;
foreach (string prop in property.Split('.')) //Dig for deep property
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
//Get deep property's type
var enumtype = type.GetGenericArguments()[0];
//Declare parameter for inner lambda
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
//Inner Collection lambda logic
//Property for inner lambda
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
//Contains method call .Contains(y.Id)
var containsMethodExp = Expression.Call(typeof(Enumerable), "Contains", new[] { propExp.Type }, _value, propExp);
//Create Expression<Func<enumtype, bool>>
var innerDelegateType = typeof(Func<,>).MakeGenericType(enumtype, typeof(bool));
//Create Inner lambda y => _vals.Contains(y.Id)
var innerFunction = Expression.Lambda(innerDelegateType, containsMethodExp, tpe);
//Get Any method info
var anyMethod = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(enumtype);
//Call Any with inner function .Any(y => _vals.Contains(y.Id))
var outerFunction = Expression.Call(anyMethod, expr, innerFunction);
//Call Where
MethodCallExpression whereCallExpression = Expression.Call
(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(outerFunction, new ParameterExpression[] { param })
);
//Create and return query
return source.Provider.CreateQuery<T>(whereCallExpression);
}
I hope this helps anyone trying to develop a similar solution.
Working with expression trees can be very hard and frustrating at first, but it's a really powerful tool once you get the hold of it.
If you have access to the dynamic keyword from c# 4.0, you might be able to work around the problem like this:
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where(obj => { dynamic x = obj;
return x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any();
}
);
But I don't know enough about the details of the problem you want to solve to say for sure.
I have looked at few examples here Calling a Method from an Expression and on MSDN but I have not been able to get the right method call/object type for Any() for the query below. I seem to be able to get the property call but not IEnumerable part of the child property.
billing_map_set_lu is the parent of billmaps_lu and is defined as an association in the Entity Framework.
The reason I am using expression trees is that
I need to be able to define the query at runtime with 1-n .SelectMany(p => p.billmaps_lu).Where(predicate) clauses. So I figured if I could build the expression trees I could handle all the different combinations I have for this system which are many.
var myResults = ctx.billing_map_set_lu
.Where(p => p.billmaps_lu.Any(b => b.billmap_columnname == "templatesittings_key" && b.billmap_columnvalue == 428264))
SelectMany(p => p.billmaps_lu)
.Where (b =>b.billmap_columnname =="locations_key" && b.billmap_columnvalue == 12445)
Select(z => z.billing_map_set_lu);
I have tried a quite a few attempts using the samples above...
ParameterExpression bms = Expression.Parameter(typeof(billmaps_lu));
Expression left1 = Expression.Property(bms, typeof(billmaps_lu).GetProperty("billmap_columnname"));
Expression right1 = Expression.Constant("templatesittings_key", typeof(string));
Expression InsideAny1 = Expression.Equal(left1, right1);
Expression left2 = Expression.Property(bms, typeof(billmaps_lu).GetProperty("billmap_columnvalue"));
Expression right2 = Expression.Constant(428264, typeof(int));
Expression InsideAny2 = Expression.Equal(left2, right2);
Expression myWhereClause1 = Expression.AndAlso(InsideAny1, InsideAny2);
The above part seems fine but when I try to do the .Any It is like I can't get the right property/method to get the right objects out. (I feel like I am on a physics problem where I am working with the wrong units.) I am hoping it is something simple that I am missing, I am pretty new to Expression Trees.. I have included non-working code to try to show you where my head is at and how someone can steer me in the right direction.
MethodInfo method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(billing_map_set_lu).GetProperty("billmaps_lu").PropertyType);
ParameterExpression billMapSetParameter = Expression.Parameter(typeof(billing_map_set_lu), "p");
ParameterExpression billMaps = Expression.Parameter(typeof(billmaps_lu), "p1");
var myFunction = Expression.Lambda<Func<billmaps_lu, bool>>(Expression.Call(method, Expression.Property(billMapSetParameter, typeof(billing_map_set_lu).GetProperty("billmaps_lu")), myWhereClause1), billMaps)
Disclaimer, I haven't got any compiled working code.
2 problems.
First problem probably lies in:
ParameterExpression billMapSetParameter = Expression.Parameter(typeof(billing_map_set_lu), "p");
That's not a parameter you need in:
Expression.Lambda<Func<billmaps_lu, bool>>(Expression.Call(method, Expression.Property(**billMapSetParameter**, typeof(billing_map_set_lu).GetProperty("billmaps_lu")), myWhereClause1), billMaps)
Change the billMapSetParameter to the billMaps ParamterExpression, then you should be good to go. You are calling the PropertyExpression to obtain your billMapSet for you from the ParameterExpression.
2nd problem: (Not sure, but my gut feeling)
You may need to pass the Where clause as a ConstantExpression with type Expression<.Func<>>. .Any method takes two parameters, of which, the second is an Expression<.Func<>> (Or just a Func<>? can't remember).
var whereExpression = Expression.Lambda<.Func<.billmaps_lu, bool>>(myWhereClause1, bms);
var ce = Expression.Constant(whereExpression)
Then pass back ce into originally where you "myWhereClause1" is.
Cross finger it works
Edit- Scrap that, SHOW MI ZEH CODEZ
public class Foo
{
public List<string> Strings { get; set; }
}
class Program
{
static void Main(string[] args)
{
Func<Foo, bool> func =
a => a.Strings.Any(b => b == "asdf");
// b => b == "asdf";
var bParameter = Expression.Parameter(typeof (string));
var asdfConstant = Expression.Constant("asdf");
var compare = Expression.Equal(bParameter, asdfConstant);
var compareExpression = Expression.Lambda<Func<string, bool>>(compare, bParameter);
var ceCompareExpression = Expression.Constant(compareExpression.Compile());
// a => a.Strings.Any(compareExpression)
var parameter = Expression.Parameter(typeof (Foo));
var foosProperty = Expression.Property(parameter, typeof (Foo).GetProperty("Strings"));
MethodInfo method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(string));
var anyMethod = Expression.Call(method, foosProperty, ceCompareExpression);
var lambdaExpression = Expression.Lambda<Func<Foo, bool>>(anyMethod, parameter);
// Test.
var foo = new Foo {Strings = new List<string> {"asdf", "fdsas"}};
Console.WriteLine(string.Format("original func result: {0}", func(foo)));
Console.Write(string.Format("constructed func result: {0}", lambdaExpression.Compile()(foo)));
Console.ReadKey();
}
}