获取友好的枚举名称作为 IQueryable
Get Friendly Enum Name as IQueryable
我们曾经将枚举存储在数据库 table 中,该数据库 Code
属性 与应用程序中的枚举相对应。这样做的目的是我们可以给数据库中的枚举一个友好的名称,这样我们就可以在需要时轻松访问它。
最近我们停止在数据库中使用 Enum table,并在每个 Enum 上使用 Description 属性,并使用反射获取 Description 作为友好名称。这很棒,因为这意味着我们在数据库中的 table 数量减少了。
这里是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
),我们不能在上面使用扩展方法获得友好名称的枚举 Entity Framework 无法识别方法。
当前代码如下所示。 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();
一般来说,您应该将 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
。
然后我们将使用自定义扩展方法,该方法在查询表达式树中找到 Description
方法调用,并将其替换为上述值以描述基于 [=19= 的翻译表达式] 呼叫类型。与表达式树一样,处理是使用自定义 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();
我们曾经将枚举存储在数据库 table 中,该数据库 Code
属性 与应用程序中的枚举相对应。这样做的目的是我们可以给数据库中的枚举一个友好的名称,这样我们就可以在需要时轻松访问它。
最近我们停止在数据库中使用 Enum table,并在每个 Enum 上使用 Description 属性,并使用反射获取 Description 作为友好名称。这很棒,因为这意味着我们在数据库中的 table 数量减少了。
这里是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
),我们不能在上面使用扩展方法获得友好名称的枚举 Entity Framework 无法识别方法。
当前代码如下所示。 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();
一般来说,您应该将 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
。
然后我们将使用自定义扩展方法,该方法在查询表达式树中找到 Description
方法调用,并将其替换为上述值以描述基于 [=19= 的翻译表达式] 呼叫类型。与表达式树一样,处理是使用自定义 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();