按枚举说明排序
我首先使用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; } }
这是Search
和SortAndPaginate
函数:
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 Yuliawan
和Michael 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(); } } }
- 为什么我在这里“无法访问封闭的流”?
- asp.net mvc中自定义属性的执行优先级
- 反序列化大型json对象时出现JsonMaxLengthexception
- Fluentvalidation,Asp.NET Core列表中每个项目的不同validation
- 何时使用Ninject和MongoDB使用Singleton vs Transient vs Request
- 如何在_Layout.cshtml中的导航栏中创建徽章接收值
- 将多个属性的组validation消息一起组成一个消息asp.net mvc
- 将HttpPostedFileBase传递给控制器方法
- 如何在模型和ViewModel中“干掉”C#属性?