按enum描述排序

本文关键字:排序 描述 enum | 更新日期: 2023-09-27 18:14:21

我正在做一个ASP。. NET MVC项目首先使用EF代码,我面临的情况是,我需要通过枚举描述来订购:

public partial class Item
{
    public enum MyEnumE
    {
        [Description("description of enum1")]
        Enum1,
        [Description("description of enum2")]
        Enum2,
        ...
    }
    public MyEnumE MyEnum { get; set; }
}

SearchSortAndPaginate函数:

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>();
    }

问题是Item表可能非常大。
我想在entities.Items.Where(itemFilter)之后调用ToListAsync,但这将返回所有过滤的项目,尽管我只需要一个页面。听起来不像是个好主意。

但是如果我不这样做,EF就不会知道GetDescription()的方法,我只能想到两个解决方案:
-将我的数据库列更改为字符串(枚举描述)而不是枚举本身(但听起来像一个黑客对我来说)
-或者直接在enum声明中按字母顺序排列MyEnumE组件(看起来很脏,也很不可维护)

我很困,因为我担心性能,如果我调用ToListAsync过滤后,所有其他解决方案似乎很脏,我绝对需要一个IPagedListSearch方法返回。

有谁知道如何处理这个问题吗?

谢谢你。

更新

下面是GetDescription方法(如果需要的话可以改变它):

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();
}

解决方案

我最终会选择Ivan Stoev的建议,因为我的项目主要基于Linq(使用Linq代替存储过程等),所以这个解决方案似乎比创建引用表更适合我的特殊情况。

然而Niyoko YuliawanMichael Freidgeim对我来说也是很好的答案,任何阅读这篇文章并有更多数据库方法的人都应该去寻找他们的解决方案;)

非常感谢你们所有人。

按enum描述排序

我会选择动态表达式。它更灵活,可以很容易地进行更改,而不会影响数据库表和查询。

但是,不是通过数据库中的描述字符串排序,而是在内存中创建有序映射,将int"顺序"值与每个enum值相关联,如下所示:

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;
    }
}

用法如下:

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;

生成如下表达式:

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

0, 1, . .N-2为按描述排序的值表中对应的索引。

替代1

您可以通过将enum投影为自定义值并按其排序来实现。

的例子:

items
    .Select(x=> new 
    {
        x,
        Desc = (
            x.Enum == Enum.One ? "Desc One" 
            : x.Enum == Enum.Two ? "Desc Two" 
            ... and so on)
    })
    .OrderBy(x=>x.Desc)
    .Select(x=>x.x);

实体框架将生成如下的SQL

SELECT
    *
FROM
    YourTable
ORDER BY
    CASE WHEN Enum = 1 THEN 'Desc One'
    WHEN Enum = 2 THEN 'Desc Two'
    ...and so on
    END

如果你有很多这样的查询,你可以创建扩展方法

public static IQueryable<Entity> OrderByDesc(this IQueryable<Entity> source)
{
    return source.Select(x=> new 
    {
        x,
        Desc = (
            x.Enum == Enum.One ? "Desc One" 
            : x.Enum == Enum.Two ? "Desc Two" 
            ... and so on)
    })
    .OrderBy(x=>x.Desc)
    .Select(x=>x.x);
}

当你需要它的时候调用它

var orderedItems = items.OrderByDesc();

替代2

另一个可选的解决方案是创建额外的表,将枚举值映射到枚举描述,并将表连接到该表。此解决方案性能更高,因为您可以在enum描述列上创建索引。


替代3

如果需要基于枚举描述属性的动态表达式,可以自己构建

助手类

public class Helper
{
    public MyEntity Entity { get; set; }
    public string Description { get; set; }
}

获取动态构建表达式

public static string GetDesc(MyEnum e)
{
    var type = typeof(MyEnum);
    var memInfo = type.GetMember(e.ToString());
    var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute),
        false);
    return ((DescriptionAttribute)attributes[0]).Description;
}
private static Expression<Func<MyEntity, Helper>> GetExpr()
{
    var descMap = Enum.GetValues(typeof(MyEnum))
        .Cast<MyEnum>()
        .ToDictionary(value => value, GetDesc);
    var paramExpr = Expression.Parameter(typeof(MyEntity), "x");
    var expr = (Expression) Expression.Constant(string.Empty);
    foreach (var desc in descMap)
    {
        // Change string "Enum" below with your enum property name in entity
        var prop = Expression.Property(paramExpr, typeof(MyEntity).GetProperty("Enum")); 
        expr = Expression.Condition(Expression.Equal(prop, Expression.Constant(desc.Key)),
            Expression.Constant(desc.Value), expr);
    }

    var newExpr = Expression.New(typeof(Helper));
    var bindings = new MemberBinding[]
    {
        Expression.Bind(typeof(Helper).GetProperty("Entity"), paramExpr),
        Expression.Bind(typeof(Helper).GetProperty("Description"), expr)
    };
    var body = Expression.MemberInit(newExpr, bindings);
    return (Expression<Func<MyEntity, Helper>>) Expression.Lambda(body, paramExpr);
}

这样命名

var e = GetExpr();
items.Select(e)
    .OrderBy(x => x.Description)
    .Select(x => x.Entity);

将我的数据库列改为字符串(枚举描述)枚举本身的(但对我来说听起来像一个hack)。

相反,对于数据驱动的应用程序,最好在数据库引用表 MyItemProperty(MyPropKey,MyPropDescription)中描述Item属性,并在Items表中包含MyPropKey列。

它有几个好处,例如

  • 允许添加新的属性值而无需更改代码;
  • 允许编写包含数据库中所有信息的SQL报告,而无需编写c#;
  • 性能优化可以在SQL上完成只需请求一个页面即可完成关卡;
  • 没有枚举,没有需要维护的代码。

为了保持简单和良好的性能,我会手动订购枚举,您只需要做一次,它将帮助很多

public enum MyEnumE
{
    Enum1 = 3,
    Enum2 = 1,
    Enum3 = 2, // set the order here... 
}

下面是一个使用join的简化示例:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace ConsoleApplication
{
    public partial class Item
    {
        public enum MyEnumE
        {
            [Description("description of enum1")]
            Enum1,
            [Description("description of enum2")]
            Enum2
        }
        public Item(MyEnumE myEnum)
        {
            MyEnum = myEnum;
        }
        public MyEnumE MyEnum { get; set; }
    }
    class Program
    {
        private static IEnumerable<KeyValuePair<int, int>> GetEnumRanks(Type enumType)
        {
            var values = Enum.GetValues(enumType);
            var results = new List<KeyValuePair<int, string>>(values.Length);
            foreach (int value in values)
            {
                FieldInfo fieldInfo = enumType.GetField(Enum.GetName(enumType, value));
                var attribute = (DescriptionAttribute)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute));
                results.Add(new KeyValuePair<int, string>(value, attribute.Description));
            }
            return results.OrderBy(x => x.Value).Select((x, i) => new KeyValuePair<int, int>(x.Key, i));
        }
        static void Main(string[] args)
        {
            var itemsList = new List<Item>();
            itemsList.Add(new Item(Item.MyEnumE.Enum1));
            itemsList.Add(new Item(Item.MyEnumE.Enum2));
            itemsList.Add(new Item(Item.MyEnumE.Enum2));
            itemsList.Add(new Item(Item.MyEnumE.Enum1));
            IQueryable<Item> items = itemsList.AsQueryable();
            var descriptions = GetEnumRanks(typeof(Item.MyEnumE));
            //foreach (var i in descriptions)
            //  Console.WriteLine(i.Value);
            var results = items.Join(descriptions, a => (int)a.MyEnum, b => b.Key, (x, y) => new { Item = x, Rank = y.Value }).OrderBy(x => x.Rank).Select(x => x.Item);
            foreach (var i in results)
                Console.WriteLine(i.MyEnum.ToString());
            Console.WriteLine("'nPress any key...");
            Console.ReadKey();
        }
    }
}

我有一个类似的问题要解决,只是我的排序必须是动态,即按列排序参数是一个string

boolean排序也必须自定义,因为truefalse之前(例如;"Active"在"Inactive"之前)。

我在这里与您分享完整的代码,以便您可以节省时间。如果你发现有改进的地方,请随时在评论中分享。

private static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> query, SortField sortField)
{
    var queryParameterExpression = Expression.Parameter(typeof(T), "x");
    var orderByPropertyExpression = GetPropertyExpression(sortField.FieldName, queryParameterExpression);
    Type orderByPropertyType = orderByPropertyExpression.Type;
    LambdaExpression lambdaExpression = Expression.Lambda(orderByPropertyExpression, queryParameterExpression);
    if (orderByPropertyType.IsEnum)
    {
        orderByPropertyType = typeof(int);
        lambdaExpression = GetExpressionForEnumOrdering<T>(lambdaExpression);
    }
    else if (orderByPropertyType == typeof(bool))
    {
        orderByPropertyType = typeof(string);
        lambdaExpression =
            GetExpressionForBoolOrdering(orderByPropertyExpression, queryParameterExpression);
    }
    var orderByExpression = Expression.Call(
        typeof(Queryable),
        sortField.SortDirection == SortDirection.Asc ? "OrderBy" : "OrderByDescending",
        new Type[] { typeof(T), orderByPropertyType },
        query.Expression,
        Expression.Quote(lambdaExpression));
    return query.Provider.CreateQuery<T>(orderByExpression);
}

共享的GetPropertyExpression已经被简化了一点,以排除嵌套的属性处理。

private static MemberExpression GetPropertyExpression(string propertyName, ParameterExpression queryParameterExpression)
{
    MemberExpression result = Expression.Property(queryParameterExpression, propertyName);
    return result;
}

下面是稍微修改过的代码(来自被接受的解决方案)来处理Enum排序。

private static Expression<Func<TSource, int>> GetExpressionForEnumOrdering<TSource>(LambdaExpression source)
{
    var enumType = source.Body.Type;
    if (!enumType.IsEnum)
        throw new InvalidOperationException();
    var body = ((int[])Enum.GetValues(enumType))
        .OrderBy(value => GetEnumDescription(value, enumType))
        .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.Convert(Expression.Constant(item.value), enumType)),
                Expression.Constant(item.ordinal),
                next));
    return Expression.Lambda<Func<TSource, int>>(body, source.Parameters[0]);
}

以及boolean排序。

private static LambdaExpression GetExpressionForBoolOrdering(MemberExpression orderByPropertyExpression, ParameterExpression queryParameterExpression)
{
    var firstWhenActiveExpression = Expression.Condition(orderByPropertyExpression,
        Expression.Constant("A"),
        Expression.Constant("Z"));
    return Expression.Lambda(firstWhenActiveExpression, new[] { queryParameterExpression });
}

GetEnumDescription也被修改为接收Type作为参数,所以它可以不需要泛型来调用。

private static string GetEnumDescription(int value, Type enumType)
{
    if (!enumType.IsEnum)
        throw new InvalidOperationException();
    var name = Enum.GetName(enumType, value);
    var field = enumType.GetField(name, BindingFlags.Static | BindingFlags.Public);
    return field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? name;
}

SortField是一个简单的抽象,包含要排序的string列属性和排序的direction。为了简单起见,我在这里也不分享这个。

干杯!