按枚举说明排序

我首先使用EF代码编写ASP.NET MVC项目,我面临的情况是需要通过枚举描述进行排序:

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> 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> SortAndPaginate(IQueryable items, SortableTypeE sortName, SortOrder.TypeE sortOrder, int pageNb) { IOrderedQueryable 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表可能非常庞大。
我想在ToListAsync entities.Items.Where(itemFilter)之后立即调用ToListAsync ,但是这将取回所有过滤的项目,尽管我只需要一个页面。 听起来不是一个好主意。

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

如果我在过滤后立即调用ToListAsync ,所有其他解决方案看起来都很脏,而且我绝对需要从Search方法返回的IPagedList ,因此我很担心性能。

有人会对如何处理这个问题有所了解吗?

非常感谢。

UPDATE

这是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对我来说也是非常好的答案,任何阅读这篇文章并拥有更多数据库方法的人都应该选择他们的解决方案;)

非常感谢你们所有人。

我会用动态表达。 它更灵活,可以轻松更改,不会影响数据库表和查询​​。

但是,我不是按照数据库中的描述字符串进行排序,而是在内存中创建有序映射,将int “order”值与每个枚举值相关联,如下所示:

 public static class EnumHelper { public static Expression> DescriptionOrder(this Expression> 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>(body, source.Parameters[0]); } public static string GetDescription(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()?.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

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

例:

 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=>xx); 

然后,entity framework将生成类似这样的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 OrderByDesc(this IQueryable 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=>xx); } 

并在需要时调用它

 var orderedItems = items.OrderByDesc(); 

备选方案2

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


备选方案3

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

助手class

 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> GetExpr() { var descMap = Enum.GetValues(typeof(MyEnum)) .Cast() .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>) Expression.Lambda(body, paramExpr); } 

像这样称呼它

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

将我的数据库列更改为字符串(枚举描述)而不是枚举本身(但听起来像是对我的黑客攻击)。

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

它有一些好处,例如

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

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

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

这是使用连接的简化示例:

 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> GetEnumRanks(Type enumType) { var values = Enum.GetValues(enumType); var results = new List>(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(value, attribute.Description)); } return results.OrderBy(x => x.Value).Select((x, i) => new KeyValuePair(x.Key, i)); } static void Main(string[] args) { var itemsList = new List(); 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 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(); } } }