Order by enum description

asp.net-mvc, c#, entity-framework, linq

Solution

I would go with dynamic expression. It's more flexible and can easily be changed w/o affecting the database tables and queries.

However, instead of sorting by description strings in the database, I would create ordered map in memory, associating `int` "order" value with each enum value like this:

public static class EnumHelper
{
    public static Expression<Func<TSource, int>> DescriptionOrder<TSource, TEnum>(this Expression<Func<TSource, TEnum>> source)
        where TEnum : struct
    {
        var enumType = typeof(TEnum);
        if (!enumType.IsEnum) throw new InvalidOperationException();

        var body = ((TEnum[])Enum.GetValues(enumType))
            .OrderBy(value => value.GetDescription())
            .Select((value, ordinal) => new { value, ordinal })
            .Reverse()
            .Aggregate((Expression)null, (next, item) => next == null ? (Expression)
                Expression.Constant(item.ordinal) :
                Expression.Condition(
                    Expression.Equal(source.Body, Expression.Constant(item.value)),
                    Expression.Constant(item.ordinal),
                    next));

        return Expression.Lambda<Func<TSource, int>>(body, source.Parameters[0]);
    }

    public static string GetDescription<TEnum>(this TEnum value)
        where TEnum : struct
    {
        var enumType = typeof(TEnum);
        if (!enumType.IsEnum) throw new InvalidOperationException();

        var name = Enum.GetName(enumType, value);
        var field = typeof(TEnum).GetField(name, BindingFlags.Static | BindingFlags.Public);
        return field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? name;
    }
}

The usage would be like this:

case SortableTypeE.Type:
    var order = EnumHelper.DescriptionOrder((Item x) => x.MyEnum);
    result = sortOrder == SortOrder.TypeE.ASC
        ? items.OrderBy(order)
        : items.OrderByDescending(order);
    result = result.ThenBy(i => i.SomeOtherProperty);
    break;

which would generate expression like this:

x => x.MyEnum == Enum[0] ? 0 :
     x.MyEnum == Enum[1] ? 1 :
     ...
     x.MyEnum == Enum[N-2] ? N - 2 :
     N - 1;

where 0,1,..N-2 is the corresponding index in the value list sorted by description.

Problem

I am working on an ASP.NET MVC projet using EF code first, and I am facing a situation where I need to order by an enum description: ``` public partial class Item { public enum MyEnumE { [Description("description of enum1")] Enum1, [Description("description of enum2")] Enum2, ... } public MyEnumE MyEnum { get; set; } } ``` Here is the `Search` and `SortAndPaginate` functions: ``` public async Task<IPagedList<Item>> Search(ItemCriteria criteria, SortableTypeE sortName, SortOrder.TypeE sortOrder, int pageNb) { var itemFilter = GenerateFilter(criteria); var items = entities.Items.Where(itemFilter); return await SortAndPaginate(items, sortName, sortOrder, pageNb); } private async Task<IPagedList<Item>> SortAndPaginate(IQueryable<Item> items, SortableTypeE sortName, SortOrder.TypeE sortOrder, int pageNb) { IOrderedQueryable<Item> result = null; switch (sortName) { ... case SortableTypeE.Type: result = sortOrder == SortOrder.TypeE.ASC ? items.OrderBy(i => i.MyEnum.GetDescription()) : items.OrderByDescending(i => i.MyEnum.GetDescription()); result = result.ThenBy(i => i.SomeOtherProperty); break; ... } if (result != null) { return await result.ToPagedListAsync(pageNb, 10); } return PagedListHelper.Empty<Item>(); } ``` The problem is that the `Item` table can be quite huge. I thought about calling `ToListAsync` right after `entities.Items.Where(itemFilter)` but this will get back all filtered items although I only need one page. Does not sound like a good idea. But if I don't do that `EF` won't know about `GetDescription()` mathod and I can only think about two solutions: - Change my database column to a string (the enum description) instead of the enum itself (but sounds like a hack to me) - Or alphabetically order `MyEnumE` components directly in the `enum` declaration (seems dirty and quite unmaintainable too) I'm quite stuck since I'm concerned about performances if I call `ToListAsync` right after filtering, all other solutions seem dirty, and I absolutely need a `IPagedList` returned from the `Search` method. Would anyone have an idea about how to deal with this issue ? Thanks a lot. UPDATE Here is the `GetDescription` method (can change it if necessary): ``` public static string GetDescription(this Enum e) { FieldInfo fi = e.GetType().GetField(e.ToString()); DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attributes.Length > 0) return attributes[0].Description; else return e.ToString(); } ``` SOLUTIONS I'll finally go for Ivan Stoev's suggestion because my project is mainly based on `Linq` (using `Linq` instead of stored procedures etc.), so this solution seems more suitable to my particular case than creating reference tables. However `Niyoko Yuliawan`'s and `Michael Freidgeim`'s are also really good answers to me, anyone reading this post and having a more database approach should go for their solutions ;) Thanks a lot to all of you.

Original source