获取友好的枚举名称作为 IQueryable

我们曾经将我们的 Enum 存储在一个数据库表中,该表具有Code与应用程序中的 Enum 对应的属性。这样做的重点是我们可以为数据库中的 Enum 提供一个友好的名称,以便我们在需要时可以轻松访问它。


最近,我们不再在数据库中使用 Enum 表,而是在每个 Enum 上使用 Description 属性,并使用反射将 Description 作为友好名称。这很棒,因为这意味着我们数据库中的表更少。


下面是Description扩展方法:


public static string Description(this Enum source)

{

    var field = source.GetType().GetField(source.ToString());


    var attributes = (DescriptionAttribute[])field.GetCustomAttributes(

        typeof(DescriptionAttribute), false);


    return attributes.Length > 0 ? attributes[0].Description : source.ToString();

}

现在,我Select在 DatabaseContext 上执行 Linq语句时遇到了问题(我需要将其保留为IQueryable),我们无法使用 Enum 上的扩展方法来获取友好名称,因为实体框架无法识别方法。


当前代码如下所示。的ItemPriority被分配,它使用反射的Enumn描述属性。此代码失败,因为 EF 无法识别该方法。


return await OrderItems(items).Skip(pageIndex * pageSize).Take(pageSize)

                .Select(item => new ItemViewModel

                {

                    Id = item.Id,

                    Description = item.Description,

                    ItemPriority = item.Priority.Description(),

                }).ToListAsync();

有没有另一种方法可以将友好名称应用于枚举,或者在数据库中使用友好名称是唯一的方法?如果我使用数据库,我可以执行以下操作:


return await OrderItems(items).Skip(pageIndex * pageSize).Take(pageSize)

                .Select(item => new ItemViewModel

                {

                    Id = item.Id,

                    Description = item.Description,

                    ItemPriority = item.Priority.Name,

                }).ToListAsync();


白衣非少年
浏览 137回答 1
1回答

牧羊人nacy

通常,您应该将enum值存储在视图模型中,并让视图使用友好描述(使用您的扩展方法)或任何它喜欢的方式对其进行格式化。但是假设出于某种原因,您需要 LINQ to Entities 查询中的该功能。可以通过动态构建 EF 兼容值来描述翻译表达式,如下所示:source == value1 ? description1 :source == value2 ? description2 :…source == valueN ? descriptionN :""为了做到这一点,首先你需要使Description方法泛型。这样,调用将包含我们稍后需要的有关实际枚举类型的信息(因为我们将处理查询表达式树,因此实际上不会调用该方法):public static class DescriptionExtensions{&nbsp; &nbsp; public static string Description<TEnum>(this TEnum source) where TEnum : struct, Enum&nbsp; &nbsp; &nbsp; &nbsp; => typeof(TEnum).GetField(source.ToString()).Description();&nbsp; &nbsp; public static string Description(this FieldInfo source)&nbsp; &nbsp; &nbsp; &nbsp; => source.GetCustomAttribute<DescriptionAttribute>()?.Description ?? source.Name;}请注意,这是利用 C# 7.3 引入的枚举约束。对于 C# 7.3 之前的版本where TEnum : struct,请typeof(TEnum).IsEnum在方法内使用和断言。然后我们将使用自定义扩展方法,该Description方法在查询表达式树中查找方法调用,并将其替换为上述值到描述转换表达式,该值基于TEnum调用的类型。与通常的表达式树一样,处理是使用 custom 完成的ExpressionVisitor。public static class QueryConverter{&nbsp; &nbsp; public static IQueryable<T> Convert<T>(this IQueryable<T> source)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var expression = new ExpressionConverter().Visit(source.Expression);&nbsp; &nbsp; &nbsp; &nbsp; if (expression == source.Expression) return source;&nbsp; &nbsp; &nbsp; &nbsp; return source.Provider.CreateQuery<T>(expression);&nbsp; &nbsp; }&nbsp; &nbsp; class ExpressionConverter : ExpressionVisitor&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; static readonly MethodInfo EnumDescriptionMethod = Expression.Call(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; typeof(DescriptionExtensions), nameof(DescriptionExtensions.Description), new[] { typeof(ExpressionType) },&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Expression.Constant(default(ExpressionType)))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .Method.GetGenericMethodDefinition();&nbsp; &nbsp; &nbsp; &nbsp; protected override Expression VisitMethodCall(MethodCallExpression node)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == EnumDescriptionMethod)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return TranslateEnumDescription(Visit(node.Arguments[0]));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return base.VisitMethodCall(node);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; static Expression TranslateEnumDescription(Expression arg)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var names = Enum.GetNames(arg.Type);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var values = Enum.GetValues(arg.Type);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Expression result = Expression.Constant("");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (int i = names.Length - 1; i >= 0; i--)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var value = values.GetValue(i);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var description = arg.Type.GetField(names[i], BindingFlags.Public | BindingFlags.Static).Description();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // arg == value ? description : ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result = Expression.Condition(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Expression.Equal(arg, Expression.Constant(value)),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Expression.Constant(description),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return result;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}现在,IQueryable<T>从包含Description调用的查询中获得 EF 兼容所需的只是在最后调用自定义Convert方法:var query = OrderItems(items)&nbsp; &nbsp; .Skip(pageIndex * pageSize)&nbsp; &nbsp; .Take(pageSize)&nbsp; &nbsp; .Select(item => new ItemViewModel&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; Id = item.Id,&nbsp; &nbsp; &nbsp; &nbsp; Description = item.Description,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ItemPriority = item.Priority.Description(),&nbsp; &nbsp; })&nbsp; &nbsp; .Convert();
打开App,查看更多内容
随时随地看视频慕课网APP