按枚举说明

时间:2016-10-23 11:31:51

标签: c# asp.net-mvc entity-framework linq

我首先使用EF代码编写ASP.NET MVC项目,我正面临需要通过枚举说明进行排序的情况:

public partial class Item
{
    public enum MyEnumE
    {
        [Description("description of enum1")]
        Enum1,
        [Description("description of enum2")]
        Enum2,
        ...
    }

    public MyEnumE MyEnum { get; set; }
}

以下是SearchSortAndPaginate函数:

public async Task<IPagedList<Item>> Search(ItemCriteria criteria, SortableTypeE sortName, SortOrder.TypeE sortOrder, int pageNb)
    {
        var itemFilter = GenerateFilter(criteria);
        var items = entities.Items.Where(itemFilter);

        return await SortAndPaginate(items, sortName, sortOrder, pageNb);
    }

    private async Task<IPagedList<Item>> SortAndPaginate(IQueryable<Item> items, SortableTypeE sortName, SortOrder.TypeE sortOrder, int pageNb)
    {
        IOrderedQueryable<Item> result = null;

        switch (sortName)
        {
            ...
            case SortableTypeE.Type:
                result = sortOrder == SortOrder.TypeE.ASC
                    ? items.OrderBy(i => i.MyEnum.GetDescription())
                    : items.OrderByDescending(i => i.MyEnum.GetDescription());
                result = result.ThenBy(i => i.SomeOtherProperty);
                break;
            ...
        }

        if (result != null)
        {
            return await result.ToPagedListAsync(pageNb, 10);
        }

        return PagedListHelper.Empty<Item>();
    }

问题是Item表可能非常庞大 我考虑在ToListAsync之后立即调用entities.Items.Where(itemFilter),但这会收回所有已过滤的项目,但我只需要一个页面。听起来不是一个好主意。

但是,如果我不这样做EF将无法了解GetDescription()方法,我只能考虑两种解决方案:
- 将我的数据库列更改为字符串(枚举描述)而不是枚举本身(但听起来像是对我的黑客攻击)
- 或者在MyEnumE声明中直接按字母顺序排列enum个组件(看起来很脏,也很难维护)

我很困难,因为如果我在过滤后立即调用ToListAsync,我很担心表演,所有其他解决方案看起来都很脏,我绝对需要从{{1}返回IPagedList方法。

有人会对如何处理这个问题有所了解吗?

非常感谢。

更新

以下是Search方法(必要时可以更改):

GetDescription

我最终会选择Ivan Stoev的建议,因为我的项目主要基于public static string GetDescription(this Enum e) { FieldInfo fi = e.GetType().GetField(e.ToString()); DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attributes.Length > 0) return attributes[0].Description; else return e.ToString(); } (使用Linq而不是存储过程等),所以这个解决方案似乎比我的特定情况更适合创建参考表。

然而,LinqNiyoko Yuliawan对我来说也是非常好的答案,任何阅读这篇文章并采用更多数据库方法的人都应该寻求解决方案;)

非常感谢大家。

6 个答案:

答案 0 :(得分:5)

我会选择动态表达。它更灵活,可以轻松更改,不会影响数据库表和查询​​。

但是,我不是按照数据库中的描述字符串进行排序,而是在内存中创建有序映射,关联int&#34; order&#34;每个枚举值的值如下:

public static class EnumHelper
{
    public static Expression<Func<TSource, int>> DescriptionOrder<TSource, TEnum>(this Expression<Func<TSource, TEnum>> source)
        where TEnum : struct
    {
        var enumType = typeof(TEnum);
        if (!enumType.IsEnum) throw new InvalidOperationException();

        var body = ((TEnum[])Enum.GetValues(enumType))
            .OrderBy(value => value.GetDescription())
            .Select((value, ordinal) => new { value, ordinal })
            .Reverse()
            .Aggregate((Expression)null, (next, item) => next == null ? (Expression)
                Expression.Constant(item.ordinal) :
                Expression.Condition(
                    Expression.Equal(source.Body, Expression.Constant(item.value)),
                    Expression.Constant(item.ordinal),
                    next));

        return Expression.Lambda<Func<TSource, int>>(body, source.Parameters[0]);
    }

    public static string GetDescription<TEnum>(this TEnum value)
        where TEnum : struct
    {
        var enumType = typeof(TEnum);
        if (!enumType.IsEnum) throw new InvalidOperationException();

        var name = Enum.GetName(enumType, value);
        var field = typeof(TEnum).GetField(name, BindingFlags.Static | BindingFlags.Public);
        return field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? name;
    }
}

用法如下:

case SortableTypeE.Type:
    var order = EnumHelper.DescriptionOrder((Item x) => x.MyEnum);
    result = sortOrder == SortOrder.TypeE.ASC
        ? items.OrderBy(order)
        : items.OrderByDescending(order);
    result = result.ThenBy(i => i.SomeOtherProperty);
    break;

会生成如下表达式:

x => x.MyEnum == Enum[0] ? 0 :
     x.MyEnum == Enum[1] ? 1 :
     ...
     x.MyEnum == Enum[N-2] ? N - 2 :
     N - 1;

其中0,1,.. N-2是按描述排序的值列表中的对应索引。

答案 1 :(得分:4)

备选方案1

您可以通过将枚举投影到自定义值并按其排序来实现。

示例:

items
    .Select(x=> new 
    {
        x,
        Desc = (
            x.Enum == Enum.One ? "Desc One" 
            : x.Enum == Enum.Two ? "Desc Two" 
            ... and so on)
    })
    .OrderBy(x=>x.Desc)
    .Select(x=>x.x);
然后

实体框架将生成类似这样的SQL

SELECT
    *
FROM
    YourTable
ORDER BY
    CASE WHEN Enum = 1 THEN 'Desc One'
    WHEN Enum = 2 THEN 'Desc Two'
    ...and so on
    END

如果你有很多这样的查询,你可以创建扩展方法

public static IQueryable<Entity> OrderByDesc(this IQueryable<Entity> source)
{
    return source.Select(x=> new 
    {
        x,
        Desc = (
            x.Enum == Enum.One ? "Desc One" 
            : x.Enum == Enum.Two ? "Desc Two" 
            ... and so on)
    })
    .OrderBy(x=>x.Desc)
    .Select(x=>x.x);
}

并在需要时调用它

var orderedItems = items.OrderByDesc();

备选方案2

另一种替代解决方案是创建额外的表,将枚举值映射到枚举描述并将表连接到此表。此解决方案将更具性能,因为您可以在枚举描述列上创建索引。

备选方案3

如果您想要基于枚举描述属性的动态表达,可以自己构建

助手等级

public class Helper
{
    public MyEntity Entity { get; set; }
    public string Description { get; set; }
}

获取动态构建的表达式

public static string GetDesc(MyEnum e)
{
    var type = typeof(MyEnum);
    var memInfo = type.GetMember(e.ToString());
    var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute),
        false);
    return ((DescriptionAttribute)attributes[0]).Description;
}

private static Expression<Func<MyEntity, Helper>> GetExpr()
{
    var descMap = Enum.GetValues(typeof(MyEnum))
        .Cast<MyEnum>()
        .ToDictionary(value => value, GetDesc);

    var paramExpr = Expression.Parameter(typeof(MyEntity), "x");
    var expr = (Expression) Expression.Constant(string.Empty);
    foreach (var desc in descMap)
    {
        // Change string "Enum" below with your enum property name in entity
        var prop = Expression.Property(paramExpr, typeof(MyEntity).GetProperty("Enum")); 
        expr = Expression.Condition(Expression.Equal(prop, Expression.Constant(desc.Key)),
            Expression.Constant(desc.Value), expr);
    }


    var newExpr = Expression.New(typeof(Helper));

    var bindings = new MemberBinding[]
    {
        Expression.Bind(typeof(Helper).GetProperty("Entity"), paramExpr),
        Expression.Bind(typeof(Helper).GetProperty("Description"), expr)
    };

    var body = Expression.MemberInit(newExpr, bindings);

    return (Expression<Func<MyEntity, Helper>>) Expression.Lambda(body, paramExpr);
}

像这样称呼

var e = GetExpr();
items.Select(e)
    .OrderBy(x => x.Description)
    .Select(x => x.Entity);

答案 2 :(得分:1)

  

将我的数据库列更改为字符串(枚举描述)   enum本身(但听起来像是对我的黑客攻击)。

相反,对于数据驱动的应用程序,最好在数据库引用表 MyItemProperty(MyPropKey,MyPropDescription)中描述Item属性,并在Items表中包含MyPropKey列。

它有一些好处,例如

  • 允许添加新的属性值而无需更改代码;
  • 允许编写包含数据库中所有信息的SQL报告而无需编写c#;
  • 可以在SQL上完成性能优化 请求一页;
  • 没有枚举 - 维护的代码更少。

答案 3 :(得分:0)

为了保持简单和良好的性能,我会手动订购枚举,你只需要做一次,它会有很多帮助

public enum MyEnumE
{
    Enum1 = 3,
    Enum2 = 1,
    Enum3 = 2, // set the order here... 
}

答案 4 :(得分:0)

以下是使用连接的简化示例:

#include <QFileInfo>
#include <QDir>
QString path = "C:\\exampleFile.txt";
if(QFileInfo(path).exists() && !QDir(path).exists()){
    //The file exists and is not a folder
}
else{
    //The file doesn't exist, either the path doesn't exist or is the path of a folder
}

答案 5 :(得分:0)

我有一个类似的问题要解决,只是我的排序必须是动态的,即按列排序参数是string

在这种意义上,boolean排序也必须自定义,truefalse之前(例如,“有效”在“无效”之前)。

我在这里与您共享完整的代码,因此您可以节省时间。如果您发现需要改进的地方,请随时发表评论。

private static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> query, SortField sortField)
{
    var queryParameterExpression = Expression.Parameter(typeof(T), "x");
    var orderByPropertyExpression = GetPropertyExpression(sortField.FieldName, queryParameterExpression);

    Type orderByPropertyType = orderByPropertyExpression.Type;
    LambdaExpression lambdaExpression = Expression.Lambda(orderByPropertyExpression, queryParameterExpression);

    if (orderByPropertyType.IsEnum)
    {
        orderByPropertyType = typeof(int);
        lambdaExpression = GetExpressionForEnumOrdering<T>(lambdaExpression);
    }
    else if (orderByPropertyType == typeof(bool))
    {
        orderByPropertyType = typeof(string);
        lambdaExpression =
            GetExpressionForBoolOrdering(orderByPropertyExpression, queryParameterExpression);
    }

    var orderByExpression = Expression.Call(
        typeof(Queryable),
        sortField.SortDirection == SortDirection.Asc ? "OrderBy" : "OrderByDescending",
        new Type[] { typeof(T), orderByPropertyType },
        query.Expression,
        Expression.Quote(lambdaExpression));

    return query.Provider.CreateQuery<T>(orderByExpression);
}

共享的GetPropertyExpression得到了简化,以排除嵌套属性处理。

private static MemberExpression GetPropertyExpression(string propertyName, ParameterExpression queryParameterExpression)
{
    MemberExpression result = Expression.Property(queryParameterExpression, propertyName);
    return result;
}

这里是经过稍微修改的代码(来自公认的解决方案),用于处理Enum排序。

private static Expression<Func<TSource, int>> GetExpressionForEnumOrdering<TSource>(LambdaExpression source)
{
    var enumType = source.Body.Type;
    if (!enumType.IsEnum)
        throw new InvalidOperationException();

    var body = ((int[])Enum.GetValues(enumType))
        .OrderBy(value => GetEnumDescription(value, enumType))
        .Select((value, ordinal) => new { value, ordinal })
        .Reverse()
        .Aggregate((Expression)null, (next, item) => next == null ? (Expression)
            Expression.Constant(item.ordinal) :
            Expression.Condition(
                Expression.Equal(source.Body, Expression.Convert(Expression.Constant(item.value), enumType)),
                Expression.Constant(item.ordinal),
                next));

    return Expression.Lambda<Func<TSource, int>>(body, source.Parameters[0]);
}

还有boolean的顺序。

private static LambdaExpression GetExpressionForBoolOrdering(MemberExpression orderByPropertyExpression, ParameterExpression queryParameterExpression)
{
    var firstWhenActiveExpression = Expression.Condition(orderByPropertyExpression,
        Expression.Constant("A"),
        Expression.Constant("Z"));

    return Expression.Lambda(firstWhenActiveExpression, new[] { queryParameterExpression });
}

GetEnumDescription被修改为接受Type作为参数,因此可以在没有泛型的情况下调用它。

private static string GetEnumDescription(int value, Type enumType)
{
    if (!enumType.IsEnum)
        throw new InvalidOperationException();

    var name = Enum.GetName(enumType, value);
    var field = enumType.GetField(name, BindingFlags.Static | BindingFlags.Public);
    return field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? name;
}

SortField是一个简单的抽象,包含要排序的string列属性和排序的direction。为了简单起见,我在这里也不会与他人分享。

干杯!