How to create expression tree for .Where (x => x. <Deep property> .Select (y => y.id) .Intersect (List <int>). Any ())

I am creating a method that receives a source Queryable<T>

, a string named / path of the property (can be a deep property, for example "TrParent.DataTypes"

to achieve this x => x.TrParent.DataTypes

) and Enumerable<int>

that contains the values ​​that I need to traverse.

Basically I proceed from the need to dynamically create the following query (I mean <DT_Det_Tr>

and TrParent.DataTypes

, which is only known at runtime, in the example is DT_Det_Tr

not a type, which 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 previous query is just an example of what I need to achieve dynamically, I really need an expression tree that creates a predicate like the one shown above, but using a dynamic type and deep nav property specified in the string.

So, I use this function to create an 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 this is what I have 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 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;
}

      

On the line, var selectCall =

I get the error:

There is no generic "Select" method on type "System.Linq.Queryable" that is compatible with the arguments and arguments of the supplied type. No type arguments should be provided unless the method is generic.

I've read a lot here about SO and other sites, but I can't get past this part, I feel like I'm running into big problems when I get to the part .Intersect(List<int>).Any()

so any help on this would be great too, thanks.

+3


source to share


2 answers


After much thought, research, and trying, I came up with a solution.

First, I made a simpler version of my target 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 did this:

var res = dbContext.Set<DT_Det_Tr>()
 .Where
 (x => x.TrParent.DataTypes
         .Any(y => _vals.Contains(y.Id))
 );

      



Which is much easier to translate to expressions (or at least it did for me) because it skips the Select call.

I got rid of the method I was using to create the deep navigation function expression and simplified it in my Intersect function because it was doing some work that I didn't really need, plus I needed access to some of the variables I am using inside it, then I did 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 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);
        }

      

Hope this helps anyone trying to develop a similar solution.

Working with expression trees can be very daunting and frustrating at first, but it is a really powerful tool once you get the hang of it.

+1


source


If you have access to the dynamic keyword from C # 4.0, you can solve this 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 be sure.

0


source







All Articles