代码之家  ›  专栏  ›  技术社区  ›  Bad Dub

将友好枚举名称获取为IQueryable

  •  2
  • Bad Dub  · 技术社区  · 6 年前

    我们曾经将枚举存储在一个数据库表中 Code 与应用程序中的枚举对应的属性。这一点是,我们可以给数据库中的枚举赋予一个友好的名称,使我们能够在需要时方便地访问它。

    最近,我们停止在数据库中使用枚举表,并在每个枚举上使用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();
    }
    

    现在我在执行Linq时遇到了这个问题 Select 关于DatabaseContext的语句(我需要将它作为 IQueryable )我们不能使用枚举的扩展方法来获得友好的名称,因为实体框架不会识别方法。

    当前代码如下所示。这个 ItemPriority 指定使用反射的枚举描述属性。由于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();
    
    1 回复  |  直到 6 年前
        1
  •  3
  •   Ivan Stoev    6 年前

    一般来说,您应该存储 enum 在视图模型中设置值,并让视图使用友好的描述(使用扩展方法)或它喜欢的任何格式对其进行格式化。

    但是,假设出于某种原因,您需要在LINQ to Entities查询中使用该功能它可以通过动态构建与EF兼容的值来实现,例如:

    source == value1 ? description1 :
    source == value2 ? description2 :
    …
    source == valueN ? descriptionN :
    
    ""
    

    为了做到这一点,首先你需要 Description 方法 通用的 . 这样,调用将包含有关实际枚举类型的信息,稍后我们将需要这些信息(因为我们将处理查询表达式树,所以实际上不会调用该方法):

    public static class DescriptionExtensions
    {
        public static string Description<TEnum>(this TEnum source) where TEnum : struct, Enum
            => typeof(TEnum).GetField(source.ToString()).Description();
    
        public static string Description(this FieldInfo source)
            => source.GetCustomAttribute<DescriptionAttribute>()?.Description ?? source.Name;
    }
    

    注意,这是利用引入的C#7.3 enum constraint . 适用于C#7.3前 where TEnum : struct 断言 typeof(TEnum).IsEnum 方法内部。

    然后我们将使用一个自定义扩展方法来查找 说明 方法在查询表达式树中调用,并将其替换为上述值以描述转换表达式,该表达式基于 TEnum 呼叫的类型。与通常的表达式树一样,处理是通过自定义 ExpressionVisitor .

    public static class QueryConverter
    {
        public static IQueryable<T> Convert<T>(this IQueryable<T> source)
        {
            var expression = new ExpressionConverter().Visit(source.Expression);
            if (expression == source.Expression) return source;
            return source.Provider.CreateQuery<T>(expression);
        }
    
        class ExpressionConverter : ExpressionVisitor
        {
            static readonly MethodInfo EnumDescriptionMethod = Expression.Call(
                typeof(DescriptionExtensions), nameof(DescriptionExtensions.Description), new[] { typeof(ExpressionType) },
                Expression.Constant(default(ExpressionType)))
                .Method.GetGenericMethodDefinition();
    
            protected override Expression VisitMethodCall(MethodCallExpression node)
            {
                if (node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == EnumDescriptionMethod)
                    return TranslateEnumDescription(Visit(node.Arguments[0]));
                return base.VisitMethodCall(node);
            }
    
            static Expression TranslateEnumDescription(Expression arg)
            {
                var names = Enum.GetNames(arg.Type);
                var values = Enum.GetValues(arg.Type);
                Expression result = Expression.Constant("");
                for (int i = names.Length - 1; i >= 0; i--)
                {
                    var value = values.GetValue(i);
                    var description = arg.Type.GetField(names[i], BindingFlags.Public | BindingFlags.Static).Description();
                    // arg == value ? description : ...
                    result = Expression.Condition(
                        Expression.Equal(arg, Expression.Constant(value)),
                        Expression.Constant(description),
                        result);
                }
                return result;
            }
        }
    }
    

    现在你只需要让EF兼容 IQueryable<T> 来自包含 说明 调用是调用自定义 Convert 最后的方法:

    var query = OrderItems(items)
        .Skip(pageIndex * pageSize)
        .Take(pageSize)
        .Select(item => new ItemViewModel
        {
            Id = item.Id,
            Description = item.Description,
             ItemPriority = item.Priority.Description(),
        })
        .Convert();