6

I'm trying to achive this conversion

"Address.Street" => (p) => p.Address.Street
"Name" => (p) => p.Name

I was able to find a method to generate an order by expression using reflection but it won't work for complex sort as Address.Street since works for a single property level.

Is there a way to do this? I've seen that I compile lambda expressions but I couldn't understand how to make it work for this case.

StackOverflower
  • 5,463
  • 13
  • 58
  • 89

1 Answers1

22

Creating an expression is not hard, but the tricky part is how to bind it to the corresponding OrderBy(Descending) / ThenBy(Descendig) methods when you don't know the type of the property (hence the type of the selector expression result).

Here is all that encapsulated in a custom extension method:

public static partial class QueryableExtensions
{
    public static IOrderedQueryable<T> OrderByMember<T>(this IQueryable<T> source, string memberPath)
    {
        return source.OrderByMemberUsing(memberPath, "OrderBy");
    }
    public static IOrderedQueryable<T> OrderByMemberDescending<T>(this IQueryable<T> source, string memberPath)
    {
        return source.OrderByMemberUsing(memberPath, "OrderByDescending");
    }
    public static IOrderedQueryable<T> ThenByMember<T>(this IOrderedQueryable<T> source, string memberPath)
    {
        return source.OrderByMemberUsing(memberPath, "ThenBy");
    }
    public static IOrderedQueryable<T> ThenByMemberDescending<T>(this IOrderedQueryable<T> source, string memberPath)
    {
        return source.OrderByMemberUsing(memberPath, "ThenByDescending");
    }
    private static IOrderedQueryable<T> OrderByMemberUsing<T>(this IQueryable<T> source, string memberPath, string method)
    {
        var parameter = Expression.Parameter(typeof(T), "item");
        var member = memberPath.Split('.')
            .Aggregate((Expression)parameter, Expression.PropertyOrField);
        var keySelector = Expression.Lambda(member, parameter);
        var methodCall = Expression.Call(
            typeof(Queryable), method, new[] { parameter.Type, member.Type },
            source.Expression, Expression.Quote(keySelector));
        return (IOrderedQueryable<T>)source.Provider.CreateQuery(methodCall);
    }
Sнаđошƒаӽ
  • 16,753
  • 12
  • 73
  • 90
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 5
    You are welcome mate :) Your words worth much more than any virtual points! – Ivan Stoev Oct 08 '16 at 17:13
  • I like too. Is it possible to have an IEnumerable version too...without the client code always having to call .AsQueryable() before calling the method? – ManOfSteele Nov 14 '16 at 21:36
  • @ManOfSteele Sure it's possible. The simplest is to define 4 similar methods for `IEnumerable` and call the corresponding methods with `source.AsQueryable()` and casting the result. Or create a similar implementation, but instead of `Expression.Call` compile the `keySelector` and call the corresponding `Enumerable` method via reflection. – Ivan Stoev Nov 14 '16 at 22:04
  • Works for me in question http://stackoverflow.com/questions/40613279/setting-a-dynamic-sort-name-field-in-a-linq-query – Saleh Nov 15 '16 at 16:26