完成对 IQueryable 通用列表进行排序的更好方法

本文关键字:排序 更好 方法 列表 IQueryable | 更新日期: 2023-09-27 18:22:02

我们使用实体框架和"POCO"生成器来创建我们的类型,这些类型被传递到各个层,它们属于(为保护无辜(命名空间Company.Application.Project.Module。 这些 POCO 对象都继承自一个基类,该基类为我们处理一些基本内容。

我想编写一个函数,该函数可以获取这些对象的集合并按属性名称对它们进行排序。

我写了以下函数 - 它完成了我想做的事情的要点,但由于几个原因我不喜欢它:

1(这不适用于任何对象类型,它必须是SortHelper类知道的对象类型(因此最后一个使用语句(。

2( "POCO"对象的类型和 BaseType 似乎不一致——取决于您在应用程序中调用此函数的位置(单元测试项目与从我们 MVP 应用程序的演示者对象调用(,这会导致我加粗的行出现问题,因为如果它抓取了错误的类型,属性就不会在其上, 在下一行中。

在表示器对象中,.GetType 显示为:ClassName_96D74E07A154AE7BDD32624F3B5D38E7F50333608A89B561218F854513E3B746...在 System.Data.Entity.DynamicProxies 命名空间中。

这就是为什么代码说.GetType((。BaseType 在那一行,它给了我:类名...在公司内.应用程序.项目.模块

但在单元测试中,.GetType(( 显示为公司中的类名.应用程序.项目.模块

和基本类型显示为BaseClass in Company.Application.Project.Module

。这更有意义,但我不理解这种不一致——这种不一致让我害怕。

3(讨厌使用反射来做这件事。

如果有人有更好的方法来做到这一点,甚至是使反射与命名空间/类型一起运行的修复程序 - 我当然会不胜感激!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Company.Application.Project.Module;
namespace Company.Application.Project
{
    public static class SortHelper
    {
        public static IOrderedQueryable<T> Sort<T>(this IQueryable<T> source, string propertyName, bool descending)
        {
            // bail out if there's nothing in the list
            if (source == null)
            {
                return null;
            }
            if (source.Count() == 0)
            {
                return source as IOrderedQueryable<T>;
            }
            // get the type -- or should it be the BaseType?  Nobody knows!
            Type sourceType = source.First().GetType().BaseType;
            // this works fine _assuming_ we got the correct type on the line above
            PropertyInfo property = sourceType.GetProperty(propertyName);
            if (descending)
            {
                return source.OrderByDescending(e => property.GetValue(e, null));
            }
            else
            {
                return source.OrderBy(e => property.GetValue(e, null));
            }
        }
    }
}

完成对 IQueryable 通用列表进行排序的更好方法

在我看来,根本没有充分的理由这样做。您在函数中使用了 OrderBy 扩展方法,为什么不直接调用它而不是此函数?除非示例中未显示某些内容,否则该函数除了弄清楚如何调用OrderBy(或OrderByDescending(然后调用它之外不会执行任何其他操作。

如果您只是想使用布尔标志在升序和降序之间切换(而不必在您尝试对某些内容进行排序的每个地方都使用 if 语句(,那么我强烈建议使用以下方法:

public static IOrderedQueryable<TSource> Sort<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, bool descending = false)
{
  if (descending) return source.OrderByDescending(keySelector);
  else return source.OrderBy(keySelector);
}

它整洁干净,您仍然可以使用 lambda 表达式来定义键,而无需依赖您的模型,您可以(可选(使用布尔标志将排序顺序更改为降序,并且没有反射。这是双赢!;)

[开始编辑以响应评论]

好的,现在我已经再次运行了VS2010,我能够找到一种方法来封装在对 ASP.NET GridView控件进行排序时使用的SortExpression,正如承诺的那样。所以它来了。

由于我们没有为这个问题定义模型,我将创建一个简单的模型。

public class Customer
{
  public int CustomerID { get; set; }
  public string Name { get; set; }
  public virtual List<Order> Orders { get; set; }
}
public class Order
{
  public int OrderID { get; set; }
  public DateTime OrderDate { get; set; }
  public decimal TotalPurchaseAmount { get; set; }
  public string Comments { get; set; }
  public bool Shipped { get; set; }
  public virtual Customer Customer { get; set; }
}

因此,让我们假设我们有一个 CustomerDetails.aspx 页面,我们希望向其添加一个 GridView,该页面将列出订单。

<asp:GridView ID="gvOrders" AutoGenerateColumns="false" runat="server"
  AllowSorting="true" OnSorting="gvOrders_Sorting">
  <Columns>
    <asp:BoundField DataField="OrderID" SortExpression="OrderID" HeaderText="Order ID" />
    <asp:BoundField DataField="OrderDate" SortExpression="OrderDate" HeaderText="Order Date" />
    <asp:BoundField DataField="TotalPurchaseAmount" SortExpression="TotalPurchaseAmount" HeaderText="Total Purchase Amount" />
    <asp:BoundField DataField="Comments" SortExpression="Comments" HeaderText="Comments" />
    <asp:BoundField DataField="Shipped" SortExpression="Shipped" HeaderText="Shipped" />
  </Columns>
</asp:GridView>

在代码隐藏中,有一个静态字典对象:

protected static readonly Dictionary<string, Expression<Func<Order, object>>> sortKeys = new Dictionary<string,Expression<Func<Order,object>>>()
{
  { "OrderID", x => x.OrderID },
  { "OrderDate", x => x.OrderDate },
  { "TotalPurchaseAmount", x => x.TotalPurchaseAmount },
  { "Comments", x => x.Comments },
  { "Shipped", x => x.Shipped }
};

然后是处理排序的函数:

protected void gvOrders_Sorting(object sender, GridViewSortEventArgs e)
{
  string exp = ViewState["gvOrders_SortExp"] as string;
  if (exp == null || exp != e.SortExpression)
  {
    e.SortDirection = SortDirection.Ascending;
    ViewState["gvOrders_SortExp"] = e.SortExpression;
    ViewState["gvOrders_SortDir"] = "asc";
  }
  else
  {
    string dir = ViewState["gvOrders_SortDir"] as string;
    if (dir == null || dir == "desc")
    {
      e.SortDirection = SortDirection.Ascending;
      ViewState["gvOrders_SortDir"] = "asc";
    }
    else
    {
      e.SortDirection = SortDirection.Descending;
      ViewState["gvOrders_SortDir"] = "desc";
    }
  }
  if (e.SortDirection == SortDirection.Ascending)
  // There's a MyCustomer property on the page, which is used to get to the Orders
  { gvOrders.DataSource = MyCustomer.Orders.OrderBy(sortKeys[e.SortExpression]); }
  else
  { gvOrders.DataSource = MyCustomer.Orders.OrderByDescending(sortKeys[e.SortExpression]); }
  gvOrders.DataBind();
}

在那里,就是这样。

我承认gvOrders_Sorting方法真的很混乱,但这就是对GridView使用自定义排序的本质。它可以进一步封装,但我决定保持简单。(我实际上开始致力于创建一个处理排序和分页的帮助程序类,但 .NET 4.0 中的自定义分页比自定义排序更糟糕!

希望你喜欢。;)

[更正]

我刚刚注意到编写的代码存在问题。 MyCustomer.Orders将是一个List<Order>而不是一个IQueryable<Order>.因此,要么将sortKeys更改为Dictionary<string, Func<Order, object>>,要么将调用更改为MyCustomer.Orders.AsQueryable().OrderBy(sortKeys[e.SortExpression])MyDbContext.Orders.Where(o => o.Customer == MyCustomer).OrderBy(sortKeys[e.SortExpression])。我把选择权留给你。;)

如果它们都继承自基类,为什么不约束泛型参数呢?

public static IOrderedQueryable<T> Sort<T>(this IQueryable<T> source, string propertyName, bool descending) where T : MyBase

这样,编译器就不会让你将一个不继承自MyBase的类传递给这个函数,你就脱离了"没人知道?!"的领域:)

只需投影要排序的属性:

 public static IOrderedQueryable<T> Sort<T, U>(this IQueryable<T> source, Func<T, U> sortExpr, bool descending)
...

source.OrderBy(e => sortExpr(e));