0

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?

Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37
user7784348
  • 265
  • 2
  • 11
  • Why are you trying to write this code via a `MethodCallExpression`? What happen(s/ed) when you write the code in the "natural" way, directly writing an `OrderBy` call? – Damien_The_Unbeliever May 11 '18 at 14:20
  • We are implementing a dynamic logic suing expression tree which can't be solved with normal linq queries. – user7784348 May 11 '18 at 14:21
  • 1
    The easiest way to solve problems like these is to write the static lambda that does this, and then look at the generated expression to see what each of the various values are, and how they differ from what you're doing here. – Servy May 11 '18 at 14:54

1 Answers1

2

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)
  );
Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37
  • You are right, but the first two steps are not really needed - the `Expression.Call` overload used by OP is exactly for such scenarios, just the `Type[]` must be populated correctly (`{ typeof(string), typeof(string) }` in this case). And of course the missing argument before the selector you've added (`companies.AsQueryable().Expression,`) but haven't mentioned in the answer. – Ivan Stoev May 13 '18 at 11:38