marcusstarnes
marcusstarnes

Reputation: 6531

Best way to specify dynamic 'OrderBy'/'OrderByDescening' within EF queries

I have an enum which contains a number of different sort orders:

public enum LogSortOrder
{
    DateTimeAsc = 1,

    DateTimeDesc = 2,

    LevelAsc = 3,

    LevelDesc = 4,

    MessageAsc = 5,

    MessageDesc = 6
}

Rather than having to build up my query within a switch statement, I wondered if there was a more efficient or elegant way of achieving this so I don't need to split my query apart and have an ugly switch block like this:

var query = context.Set<Log>().AsQueryable();

            switch ( sortOrder )
            {
                case LogSortOrder.LevelAsc:
                    query = query.OrderBy( l => l.Level );
                    break;

                case LogSortOrder.LevelDesc:
                    query = query.OrderByDescending( l => l.Level );
                    break;

                case LogSortOrder.MessageAsc:
                    query = query.OrderBy( l => l.Message );
                    break;

                case LogSortOrder.MessageDesc:
                    query = query.OrderByDescending( l => l.Message );
                    break;

                case LogSortOrder.DateTimeAsc:
                    query = query.OrderBy( l => l.Date );
                    break;

                default:
                    query = query.OrderByDescending( l => l.Date );
                    break;
            }

            return await query
                .Skip( offset )
                .Take( limit )
                .ToListAsync();

Upvotes: 3

Views: 107

Answers (3)

Kevin Aung
Kevin Aung

Reputation: 832

I think the easiest and the most straight forward way is to keep your logic but wrap it in an extension method so it looks nicer when you use it. This will be easier to maintain too.

public static class SortHelper
{
    public static IOrderedQueryable<Log> SortFromCommand(this IQueryable<Log> entities, LogSortOrder sortOrder)
    {
        IOrderedQueryable<Log> ordered;

        switch (sortOrder)
        {
            case LogSortOrder.LevelAsc:
                ordered = entities.OrderBy(l => l.Level);
                break;

            case LogSortOrder.LevelDesc:
                ordered = entities.OrderByDescending( l => l.Level );
                break;

            case LogSortOrder.MessageAsc:
                ordered = entities.OrderBy( l => l.Message );
                break;

            case LogSortOrder.MessageDesc:
                ordered = entities.OrderByDescending( l => l.Message );
                break;

            case LogSortOrder.DateTimeAsc:
                ordered = entities.OrderBy( l => l.Date );
                break;

            default:
                ordered = entities.OrderByDescending( l => l.Date );
                break;
        }

        return ordered;
    }
}

Usage:

var sortCommand = LogSortOrder.LevelAsc;
var results = context.Set<Log>().AsQueryable()
                                .SortFromCommand(sortCommand)
                                .Skip(10)
                                .ToList();

Upvotes: 2

John
John

Reputation: 17501

You could use a library that can convert strings into linq expressions like so: https://www.nuget.org/packages/System.Linq.Dynamic.Library/

At which point you could (for example) change your enum values to be strings (i.e. DateTimeDesc = "Date desc" or MessageAsc = "Message"

Then your switch statement would be reduced to:

query = query.OrderBy(sortOrder);

Upvotes: 0

Ivan Stoev
Ivan Stoev

Reputation: 205849

Well, you can build it using System.Linq.Expressions. I would not call it more efficient or elegant. Probably more flexible (allows easy adding new enum members), but at the same time more error prone.

var query = context.Set<Log>().AsQueryable();

var sortInfo = sortOrder.ToString();
bool descending = sortInfo.EndsWith("Desc");
var propertyName = sortInfo.Substring(0, sortInfo.Length - (descending ? "Desc" : "Asc").Length);
var parameter = Expression.Parameter(typeof(Log), "log");
var selector = Expression.Lambda(Expression.PropertyOrField(parameter, propertyName), parameter);
query = query.Provider.CreateQuery<Log>(Expression.Call(
    typeof(Queryable), 
    "OrderBy" + (descending ? "Descending" : null), 
    new [] { parameter.Type, selector.Body.Type},
    query.Expression, Expression.Quote(selector)));

// ...

Upvotes: 2

Related Questions