簡體   English   中英

獲取友好的枚舉名稱作為IQueryable

[英]Get Friendly Enum Name as 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();
}

現在,我在DatabaseContext上執行Linq Select語句時遇到了問題(我需要將其保留為IQueryable ),因為實體框架無法識別方法,因此我們無法在Enum上使用擴展方法來獲取友好名稱。

當前代碼如下所示。 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();

還有另一種方法可以將友好名稱應用於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使用where TEnum : struct和斷言typeof(TEnum).IsEnum

然后,我們將使用一個自定義擴展方法,該方法在查詢表達式樹中找到Description方法調用,並將其替換為上述值,以描述轉換表達式為基礎,該表達式基於調用的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;
        }
    }
}

現在,從包含Description調用的查詢中獲取與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();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM