thiagoprzy
thiagoprzy

Reputation: 421

Sort lists by using LambdaExpression in "complex types"

everyone. I've tried in the past couple of hours to find an answer with Google and desperate tests using Immediate Window, but I think I'm not making the correct questions.

I have an OrderByExtender in my application, which I use to do sorts just using the property name and a boolean to tell if it's ASC or DESC sort.

    public static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> collection, string key, bool isAscending)
    {
        LambdaExpression sortLambda = BuildLambda<T>(key);

        if (isAscending)
            return collection.OrderBy((Func<T, object>)sortLambda.Compile());
        else
            return collection.OrderByDescending((Func<T, object>)sortLambda.Compile());
    }

    private static LambdaExpression BuildLambda<T>(string key)
    {
        ParameterExpression TParameterExpression = Expression.Parameter(typeof(T), "p");
        LambdaExpression sortLambda = Expression.Lambda(Expression.Convert(Expression.Property(TParameterExpression, key), typeof(object)), TParameterExpression);
        return sortLambda;
    }

It works like a charm when I use common-type properties (string, int, etc). But now I came up with the following scenario: I have an object called BusinessOrder, and inside it there is a property with the type Quarter. This Quarter object has three properties: Year(int), Quarter(int) and Abbreviation(string). I must do the OrderBy using the Abbreviation property. In other words, I must do:

BusinessOrderList.OrderBy(b => b.Quarter.Abbreviation);

But I want to put this kind of sort possibility inside my Extender, by passing as key parameter something like "Quarter.Abbreviation" and the Extender method understands that it's a matter of getting the property inside a "complex object".

I believe there's something I could do inside the "Expression.Lambda" method that creates the sortLambda variable, but I couldn't figure out how to replicate this behavior using Expressions. Can someone help me? Thanks in advance.

Upvotes: 1

Views: 802

Answers (3)

Kahbazi
Kahbazi

Reputation: 15015

You can pass a Func for sorting your collection

public static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> collection,
                           Func<IEnumerable<T>,IOrderedEnumerable<T>> OrderBy)
{
    if(OrderBy != null)
        return OrderBy(collection);
    return collection; 
}

You have to call it like this

{
    Func<IEnumerable<Book>,IOrderedEnumerable<Book>> sort = 
    list => list.OrderBy(B=>B.Author.Name).ThenByDescending(B=>B.Title) ; 

    List.OrderBy(sort);

}

Upvotes: 0

Daniel
Daniel

Reputation: 1837

If possible, I would recommend replacing the string key parameter with a Expression<Func<T, object>> key parameter. That way, your function call can be BusinessOrderList.OrderBy(b => b.Quarter.Abbreviation); just as you described. Since all the OrderBy/OrderByDescending methods accept this expression as an alternative to a string anyway, I don't see what benefit you derive from keeping key as a string.

Upvotes: 0

Verbon
Verbon

Reputation: 545

You cannot access complex property in one step. You must build expressions chain recursively:

    private Func<TInput, object> BuildLambda<TInput>(string complexPropertyPath)
    {
        var parameter = Expression.Parameter(typeof(TInput), "p");

        var propertyPathParts = complexPropertyPath.Split('.');
        MemberExpression complexPropertyAccessExpression = null;
        foreach (var propertyPathPart in propertyPathParts)
        {
            complexPropertyAccessExpression = complexPropertyAccessExpression == null
                ? Expression.Property(parameter, propertyPathPart)
                : Expression.Property(complexPropertyAccessExpression, propertyPathPart);
        }

        var lambda = (Func<TInput, object>)Expression.Lambda(complexPropertyAccessExpression, parameter).Compile();

        return lambda;
    }

Upvotes: 2

Related Questions