bravo2zero
bravo2zero

Reputation: 139

How to call IQueryable.OrderBy() method with a runtime type for generic parameter?

I need to call the OrderBy<T, TKey>(Func<T, TKey>) method with a value for TKey only available at runtime. After reading answers on SO on how to use a variable as a generic parameter, I'm trying the following approach:

string key = "MyProperty";
Type keyType = typeof(T).GetProperty(key).PropertyType;
MethodInfo methodInfo = typeof(MyClass)
                .GetMethod(
                    "MyGenericStaticMethod"),
                    BindingFlags.NonPublic | BindingFlags.Static);
// T is known at compile time.
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(new[] { typeof(T), keyType});

var expression = genericMethodInfo.Invoke(null, new object[] { params });

myQueryable.OrderBy(expression);

The problem is, genericMethodInfo.Invoke() returns object and hence cannot be used with OrderBy() which expects an argument of type Func<T, TKey>. However, TKey can be different value types like string,int that are only known at runtime. Can this even be done and if so, how?

Upvotes: 1

Views: 257

Answers (1)

Ňuf
Ňuf

Reputation: 6217

Method MethodInfo.Invoke() is used to perform method call with provided parameters, and cannot be used to generate expression. To generate lambda Expression that can be used as an argument to .OrderBy() method, use this instead:

string key = "MyProperty";
Type keyType = typeof(T).GetProperty(key).PropertyType;
MethodInfo methodInfo = typeof(MyClass)
    .GetMethod(
    "MyGenericStaticMethod",
    BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(new[] { typeof(T), keyType });

//this represents parameter of keySelector expression used in OrderBy method
var parameterExpression = Expression.Parameter(typeof(T));

// Expression representing call to MyGenericStaticMethod
var expression = Expression.Call(genericMethodInfo, parameterExpression);

// To use it as an argument of OrderBy method, we must convert expression to lambda
var lambda = Expression.Lambda(expression, parameterExpression);

You will probably encounter another problem: You cannot simply call myQueryable.OrderBy(lambda), because this doesn't allow compiller to infer it's generic arguments and you cannot provide these generic arguments, because TKey is not known at compile time. So you will need to do another reflection, to actually call an .OrderBy() method:

// OrderBy method has generic parameters and several overloads. It is thus 
// difficult to get it's MethodInfo just by typeof(Queryable).GetMethod().
// Although it may seem weird, but it is easier to get it's MethodInfo from
// some arbitrary expression. Generic arguments "<object, object>" does not
// matter for now, we will replace them later
Expression<Func<IQueryable<object>, IQueryable<object>>> orderByExpression =
    x => x.OrderBy<object, object>((o) => null);

// Replace generic parameters of OrderBy method with actual generic arguments
var orderByMethodInfo = (orderByExpression.Body as MethodCallExpression)
    .Method
    .GetGenericMethodDefinition()
    .MakeGenericMethod(new[] { typeof(T), keyType });

// Now we are finally ready to call OrderBy method
var orderedResultQuery = orderByMethodInfo.Invoke(
    null,
    new Object[] { myQueryable, lambda })
    as IQueryable<T>;

// Just for testing purpose, let's materialize result to list
var orderedResult = orderedResultQuery.ToList();

Upvotes: 1

Related Questions