So the first thing that we'll need is a method that can get the selector expression that selects out that property value when given the property name. It will need to build the expression from scratch:
public static Tuple<Expression, Type> GetSelector<T>(IEnumerable<string> propertyNames)
{
var parameter = Expression.Parameter(typeof(T));
Expression body = parameter;
foreach (var property in propertyNames)
{
body = Expression.Property(body,
body.Type.GetProperty(property));
}
return Tuple.Create(Expression.Lambda(body, parameter) as Expression
, body.Type);
}
Note that since this results in a chain of properties the method also returns the type of the final property, as that wouldn't be a particularly easy bit of information to access from the caller's perspective.
Because we don't know the return type of the property when calling Selector so we have no choice but to leave the return type of this method typed to Expression
. We can't cast it to an Expression<Func<T, Something>>
. We could have it return a Expression<Func<T, object>>
, and that would work for all properties that select out a reference type, but this wouldn't be able to box value types, so it would throw a runtime exception in those cases.
Now, because we don't know the exact type of that expression we can't call OrderBy
or OrderByDescending
directly. We need to grab those methods through reflection and use MakeGenericMethod
so that they can be created using the proper type based on inspection of that property using reflection.
public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(
this Tuple<IEnumerable<string>, SortingType> sortCriteria)
{
var selector = GetSelector<T>(sortCriteria.Item1);
Type[] argumentTypes = new[] { typeof(T), selector.Item2 };
var orderByMethod = typeof(Queryable).GetMethods()
.First(method => method.Name == "OrderBy"
&& method.GetParameters().Count() == 2)
.MakeGenericMethod(argumentTypes);
var orderByDescMethod = typeof(Queryable).GetMethods()
.First(method => method.Name == "OrderByDescending"
&& method.GetParameters().Count() == 2)
.MakeGenericMethod(argumentTypes);
if (sortCriteria.Item2 == SortingType.Descending)
return query => (IOrderedQueryable<T>)
orderByDescMethod.Invoke(null, new object[] { query, selector.Item1 });
else
return query => (IOrderedQueryable<T>)
orderByMethod.Invoke(null, new object[] { query, selector.Item1 });
}