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

坏配音

我们过去将枚举存储在数据库表中,该表具有Code与应用程序中的枚举相对应属性。这样做的目的是,我们可以为数据库中的枚举取一个友好的名称,以便我们在需要时可以轻松地访问它。

最近,我们停止在数据库中使用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();

还有另一种方法可以将友好名称应用到Enums,还是将友好名称包含在数据库中是唯一的方法?如果使用数据库,则可以执行以下操作:

return await OrderItems(items).Skip(pageIndex * pageSize).Take(pageSize)
                .Select(item => new ItemViewModel
                {
                    Id = item.Id,
                    Description = item.Description,
                    ItemPriority = item.Priority.Name,
                }).ToListAsync();
伊万·斯托夫

通常,您应该将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约束对于C#7.3之前的版本where TEnum : struct,请typeof(TEnum).IsEnum在方法内部使用并声明

然后,我们将使用一个自定义扩展方法,该Description方法在查询表达式树内找到该方法调用,并将其替换为上述值,以基于TEnum该调用类型来描述翻译表达式与表达式树一样,处理过程是使用custom进行的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;
        }
    }
}

现在,IQueryable<T>要从包含Description调用的查询中获得EF兼容,所需要做的就是最后调用自定义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();

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章