user7784348
user7784348

Reputation: 265

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?

Upvotes: 0

Views: 185

Answers (1)

Aleks Andreev
Aleks Andreev

Reputation: 7054

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)
  );

Upvotes: 2

Related Questions