对EF4数据执行动态排序

时间:2010-05-03 17:04:16

标签: sql-server-2008 visual-studio-2010 linq-to-entities entity-framework-4

我正在尝试对我正在将网格放入MVC UI的数据进行动态排序。由于MVC是通过WCF从其他所有内容中抽象出来的,因此我创建了一些实用程序类和扩展来帮助解决这个问题。两个最重要的事情(略微简化)如下:

    public static IQueryable<TModel> ApplySortOptions<TModel, TProperty>(this IQueryable<TModel> collection, IEnumerable<ISortOption<TModel, TProperty>> sortOptions) where TModel : class
    {
        var sortedSortOptions = (from o in sortOptions
                                 orderby o.Priority ascending
                                 select o).ToList();

        var results = collection;

        foreach (var option in sortedSortOptions)
        {
            var currentOption = option;
            var propertyName = currentOption.Property.MemberWithoutInstance();
            var isAscending = currentOption.IsAscending;

            if (isAscending)
            {
                results = from r in results
                          orderby propertyName ascending 
                          select r;
            }
            else
            {
                results = from r in results
                          orderby propertyName descending 
                          select r;
            }
        }

        return results;
    }


public interface ISortOption<TModel, TProperty> where TModel : class
{
    Expression<Func<TModel, TProperty>> Property { get; set; }
    bool IsAscending { get; set; }
    int Priority { get; set; }
}

我没有给你MemberWithoutInstance()的实现,但请相信我,它会将属性的名称作为字符串返回。 : - )

以下是我将如何使用它的示例(使用ISortOption<TModel, TProperty>的非有趣的基本实现):

var query = from b in CurrentContext.Businesses
            select b;

var sortOptions = new List<ISortOption<Business, object>>
                      {
                          new SortOption<Business, object>
                              {
                                  Property = (x => x.Name),
                                  IsAscending = true,
                                  Priority = 0
                              }
                      };

var results = query.ApplySortOptions(sortOptions);

正如我在this question中发现的那样,问题特定于我的orderby propertyName ascendingorderby propertyName descending行(就我所知,其他一切都很有效)。如何以动态/通用的方式正常工作?

2 个答案:

答案 0 :(得分:2)

您应该真正考虑使用Dynamic LINQ。实际上,您可以选择仅按名称列出属性而不是使用表达式,从而使构造更容易。

public static IQueryable<T> ApplySortOptions<T, TModel, TProperty>(this IQueryable<T> collection, IEnumerable<ISortOption<TModel, TProperty>> sortOptions) where TModel : class  
{    
    var results = collection;  

    foreach (var option in sortOptions.OrderBy( o => o.Priority ))  
    {  
        var currentOption = option;  
        var propertyName = currentOption.Property.MemberWithoutInstance();  
        var isAscending = currentOption.IsAscending;  

         results = results.OrderBy( string.Format( "{0}{1}", propertyName, !isAscending ? " desc" : null ) );
    }  

    return results;  
}

答案 1 :(得分:0)

虽然我认为@ tvanfosson的解决方案将完美运行,但我也在考虑这种可能性:

    /// <summary>
    /// This extension method is used to help us apply ISortOptions to an IQueryable.
    /// </summary>
    /// <param name="collection">This is the IQueryable you wish to apply the ISortOptions to.</param>
    /// <param name="sortOptions">These are the ISortOptions you wish to have applied. You must specify at least one ISortOption (otherwise, don't call this method).</param>
    /// <returns>This returns an IQueryable object.</returns>
    /// <remarks>This extension method should honor deferred execution on the IQueryable that is passed in.</remarks>
    public static IOrderedQueryable<TModel> ApplySortOptions<TModel, TProperty>(this IQueryable<TModel> collection, IEnumerable<ISortOption<TModel, TProperty>> sortOptions) where TModel : class
    {
        Debug.Assert(sortOptions != null, "ApplySortOptions cannot accept a null sortOptions input.");
        Debug.Assert(sortOptions.Count() > 0, "At least one sort order must be specified to ApplySortOptions' sortOptions input.");

        var firstSortOption = sortOptions.OrderBy(o => o.Priority).First();
        var propertyName = firstSortOption.Property.MemberWithoutInstance();
        var isAscending = firstSortOption.IsAscending;

        // Perform the first sort action
        var results = isAscending ? collection.OrderBy(propertyName) : collection.OrderByDescending(propertyName);

        // Loop through all of the rest ISortOptions
        foreach (var sortOption in sortOptions.OrderBy(o => o.Priority).Skip(1))
        {
            // Make a copy of this or our deferred execution will bite us later.
            var currentOption = sortOption;

            propertyName = currentOption.Property.MemberWithoutInstance();
            isAscending = currentOption.IsAscending;

            // Perform the additional orderings.
            results = isAscending ? results.ThenBy(propertyName) : results.ThenByDescending(propertyName);
        }

        return results;
    }

使用此questionanswer代码:

    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
    {
        return ApplyOrder(source, property, "OrderBy");
    }

    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
    {
        return ApplyOrder(source, property, "OrderByDescending");
    }

    public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
    {
        return ApplyOrder(source, property, "ThenBy");
    }

    public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
    {
        return ApplyOrder(source, property, "ThenByDescending");
    }

    private static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
    {
        var props = property.Split('.');
        var type = typeof (T);
        var arg = Expression.Parameter(type, "x");
        Expression expr = arg;
        foreach (var prop in props)
        {
            // use reflection (not ComponentModel) to mirror LINQ
            var pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        var delegateType = typeof (Func<,>).MakeGenericType(typeof (T), type);
        var lambda = Expression.Lambda(delegateType, expr, arg);

        var result = typeof (Queryable).GetMethods().Single(
            method => method.Name == methodName
                      && method.IsGenericMethodDefinition
                      && method.GetGenericArguments().Length == 2
                      && method.GetParameters().Length == 2)
            .MakeGenericMethod(typeof (T), type)
            .Invoke(null, new object[] {source, lambda});
        return (IOrderedQueryable<T>) result;
    }