Search code examples
c#expression-trees

Expression Trees OrderBy Exception


I have below code snippet and getting an error as mentioned below.

string[] companies = {
 "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
 "Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
 "Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
 "Blue Yonder Airlines", "Trey Research", "The Phone Company",
 "Wingtip Toys", "Lucerne Publishing", "Fourth Coffee"
};

var exp = companies.AsQueryable<string>();

// Compose the expression tree that represents the parameter to the predicate.  
ParameterExpression pe = Expression.Parameter(typeof(string), "company");

// The IQueryable data to query.  
IQueryable<String> queryableData = companies.AsQueryable<string>();

MethodCallExpression orderByCallExpression1 = Expression.Call(
    typeof(Queryable),
    "OrderBy",
    new Type[] { queryableData.ElementType },
    Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe })
  );

System.InvalidOperationException: 'No generic method 'OrderBy' 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. '

Please guide whats wrong in here?


Solution

  • There are two "problems" with OrderBy method: it has overloads and it is generic. First you need to select correct overload:

    var openOrderBy = typeof(Queryable)
        .GetMethods(BindingFlags.Static | BindingFlags.Public)
        .First(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
    

    (note: here I'm using simple check based on parameters count, you can read more here)

    Then you need to bind it to concrete types:

    var closedOrderBy = openOrderBy.MakeGenericMethod(
        typeof(string), // type of item in collection, TSource
        typeof(string)); // type returned by lambda, TKey
    

    Now you can use that MethodInfo in Expression.Call:

    var pe = Expression.Parameter(typeof(string), "company");
    
    var orderByCall = Expression.Call(null, // for static methods
        closedOrderBy,
        companies.AsQueryable().Expression,
        Expression.Lambda<Func<string, string>>(pe, pe));
    

    You can test it with extra lambda:

    var result = Expression.Lambda<Func<IQueryable<string>>>(orderByCall)
        .Compile().Invoke().ToList();
    
    result.ForEach(Console.WriteLine);
    

    Demo


    Update: as @Ivan Stoev said in comment, answer can be simplified

    var queryableData = companies.AsQueryable();
    var pe = Expression.Parameter(typeof(string), "company");
    
    var orderByCall = Expression.Call(typeof(Queryable),
        "OrderBy",
        new []{ queryableData.ElementType,
                queryableData.ElementType }, // <-- fix #1 select correct overload
        queryableData.Expression,            // <-- fix #2 pass first argument
        Expression.Lambda<Func<string, string>>(pe, pe)
      );