1

I'm trying to build the following lambda expression using the expression tree ->

info => info.event_objects.Select(x => x.object_info.contact_info)

I researched a lot and find some answers on the StackOverflow. This one helped me to build the

info => 
     info.event_objects.Any(x => x.object_info.contact_info.someBool == true)

As you can see, the method 'Any' is easy to get.

var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" 
&& m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);

The main problem is with the method 'Select'. If you will try to change the Name "Any" to "Select", you will get the following exception:

var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == 
"Select" && m.GetParameters().Length == 2);
selectMethod = selectMethod.MakeGenericMethod(childType);

Additional information: Sequence contains more than one matching element

Another way I've tried:

MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name 
  == "Select"))
    foreach (ParameterInfo p in m.GetParameters().Where(p => 
           p.Name.Equals("selector")))
        if (p.ParameterType.GetGenericArguments().Count() == 2)
            selectMethod = (MethodInfo)p.Member;

It seems work, but then I get the exception here:

navigationPropertyPredicate = Expression.Call(selectMethod, parameter, 
navigationPropertyPredicate);

 Additional information: Method 
 System.Collections.Generic.IEnumerable`1[TResult] Select[TSource,TResult] 
 (System.Collections.Generic.IEnumerable`1[TSource], 
 System.Func`2[TSource,TResult]) is a generic method definition> 

After that, I've tried to use:

selectMethod = selectMethod.MakeGenericMethod(typeof(event_objects), 
typeof(contact_info));

In fact, it doesn't help.

Here is my full code

 public static Expression GetNavigationPropertyExpression(Expression parameter, params string[] properties)
    {
        Expression resultExpression = null;
        Expression childParameter, navigationPropertyPredicate;
        Type childType = null;

        if (properties.Count() > 1)
        {
            //build path
            parameter = Expression.Property(parameter, properties[0]);
            var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
            //if it´s a collection we later need to use the predicate in the methodexpressioncall
            if (isCollection)
            {
                childType = parameter.Type.GetGenericArguments()[0];
                childParameter = Expression.Parameter(childType, "x");
            }
            else
            {
                childParameter = parameter;
            }
            //skip current property and get navigation property expression recursivly
            var innerProperties = properties.Skip(1).ToArray();
            navigationPropertyPredicate = GetNavigationPropertyExpression(childParameter, innerProperties);
            if (isCollection)
            {
                //var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Select" && m.GetParameters().Length == 2);
                //selectMethod = selectMethod.MakeGenericMethod(childType);
                MethodInfo selectMethod = null;
                foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name == "Select"))
                    foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("selector")))
                        if (p.ParameterType.GetGenericArguments().Count() == 2)
                            selectMethod = (MethodInfo)p.Member;

                navigationPropertyPredicate = Expression.Call(selectMethod, parameter, navigationPropertyPredicate);
                resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
            }
            else
            {
                resultExpression = navigationPropertyPredicate;
            }
        }
        else
        {
            var childProperty = parameter.Type.GetProperty(properties[0]);
            var left = Expression.Property(parameter, childProperty);
            var right = Expression.Constant(true, typeof(bool));
            navigationPropertyPredicate = Expression.Lambda(left);
            resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
        }
        return resultExpression;
    }


    private static Expression MakeLambda(Expression parameter, Expression predicate)
    {
        var resultParameterVisitor = new ParameterVisitor();
        resultParameterVisitor.Visit(parameter);
        var resultParameter = resultParameterVisitor.Parameter;
        return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
    }

    private class ParameterVisitor : ExpressionVisitor
    {
        public Expression Parameter
        {
            get;
            private set;
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            Parameter = node;
            return node;
        }
    }


    [TestMethod]
    public void TestDynamicExpression()
    {
        var parameter = Expression.Parameter(typeof(event_info), "x");
        var expression = GetNavigationPropertyExpression(parameter, "event_objects", "object_info", "contact_info");
    }

Edit: unfortunately, I've tried answers from this question, but it doesn't seem work

AlexZholob
  • 317
  • 1
  • 16
  • Possible duplicate of [c# - Dynamically generate linq select with nested properties](https://stackoverflow.com/questions/51753165/c-sharp-dynamically-generate-linq-select-with-nested-properties) – Emre Savcı Aug 17 '18 at 11:35
  • Hope my aim was clear if someone considers that there isn't enough information regarding this, or something isn't understandable I will do my best to inform you as quickly as possible. – AlexZholob Aug 17 '18 at 11:36
  • 1
    @mjwills I think that there are more than one select method found – AlexZholob Aug 17 '18 at 11:37
  • @Emre, thanks, I checked those, but unfortunately couldn't apply this answer, as the implementation is different, or maybe I just missed something :( – AlexZholob Aug 17 '18 at 11:51
  • 1
    @AlexZholob 1. `Select(IE source, Func selector)` 2. `Select(IE source, Func selector)` the second Select method is the overload which takes the "(item, index) => " selector lambda – Vladi Pavelka Aug 17 '18 at 11:54
  • @VladiPavelka, understood, but how can I get the proper select method? – AlexZholob Aug 17 '18 at 11:59

2 Answers2

2

You can avoid finding the correct generic method overload via reflection (which is complicated and error prone as you already noticed) by using one of the two Expression.Call method overloads (one for static and one for instance methods) accepting string methodName and Type[] typeArguments.

Also the current implementation is overcomplicated and contains other problems, due to the lack of clear separation of expression and lambda expression building.

Here is a correct working implementation:

public static LambdaExpression GetNavigationPropertySelector(Type type, params string[] properties)
{
    return GetNavigationPropertySelector(type, properties, 0);
}

private static LambdaExpression GetNavigationPropertySelector(Type type, string[] properties, int depth)
{
    var parameter = Expression.Parameter(type, depth == 0 ? "x" : "x" + depth);
    var body = GetNavigationPropertyExpression(parameter, properties, depth);
    return Expression.Lambda(body, parameter);
}

private static Expression GetNavigationPropertyExpression(Expression source, string[] properties, int depth)
{
    if (depth >= properties.Length)
        return source;
    var property = Expression.Property(source, properties[depth]);
    if (typeof(IEnumerable).IsAssignableFrom(property.Type))
    {
        var elementType = property.Type.GetGenericArguments()[0];
        var elementSelector = GetNavigationPropertySelector(elementType, properties, depth + 1);
        return Expression.Call(
            typeof(Enumerable), "Select", new Type[] { elementType, elementSelector.Body.Type },
            property, elementSelector);
    }
    else
    {
        return GetNavigationPropertyExpression(property, properties, depth + 1);
    }
}

The first is the public method. It internally uses the next two private methods to recursively build the desired lambda. As you can see, I distinguish between building lambda expression and just expression to be used as lambda body.

Test:

var selector = GetNavigationPropertySelector(typeof(event_info), 
    "event_objects", "object_info", "contact_info");

Result:

x => x.event_objects.Select(x1 => x1.object_info.contact_info)
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
0

"Additional information: Sequence contains more than one matching element"

Unlike "Any()", for "Select()" there are two overloads with two parameters:

  1. Select<TS, TR>(IE<TS> source, Func<TS, TR> selector)
  2. Select<TS, TR>(IE<TS> source, Func<TS, int, TR> selector) (takes the "(item, index) => " selector lambda)

Since your code already relies on "esoteric knowledge" anyway, just take the first one of them:

var selectMethod = typeof(Enumerable).GetMethods()
        .First(m => m.Name == nameof(Enumerable.Select) 
                 && m.GetParameters().Length == 2);
Vladi Pavelka
  • 916
  • 4
  • 12
  • Thanks for the reply, for now, I need to make select method generic using: selectMethod = selectMethod.MakeGenericMethod(). But whatever I pass as the parameter to MakeGenericMethod(), I get exceptions. Is there any ideas? – AlexZholob Aug 17 '18 at 12:51
  • I've tried the input and output parameters, but it doesn't seem work, and always through an exception – AlexZholob Aug 17 '18 at 12:56
  • @Alex, `Select()` is a generic method, with 2 generic parameters. To obtain the "constructed" version, you need to replace those 2 with a concrete type, say int & bool (to get `Select()`). That's actually what the [`MakeGenericMethod(Type[] typeArguments)`](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.methodinfo.makegenericmethod?view=netframework-4.7.2) is good for. Select has 2 generic parameters -> you need to call the MakeGenericMethod() with two type arguments – Vladi Pavelka Aug 17 '18 at 13:10