C# 完成IQueryable通用列表排序的更好方法?

C# 完成IQueryable通用列表排序的更好方法?,c#,.net,linq,entity-framework,iqueryable,C#,.net,Linq,Entity Framework,Iqueryable,我们正在使用实体框架和“POCO”生成器来创建我们的类型,这些类型被传递到各个层,它们属于名称空间Company.Application.Project.Module(更改为保护无辜)。这些POCO对象都继承自一个基类,该基类为我们处理一些基本内容 我想写一个函数,它可以获取这些对象的集合并按属性名对它们进行排序 我已经编写了以下函数——它完成了我想做的事情的要点,但出于几个原因我不喜欢它: 1) 这对任何对象类型都不起作用,它必须是SortHelper类知道的对象类型(因此是最后一个using

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

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

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

1) 这对任何对象类型都不起作用,它必须是SortHelper类知道的对象类型(因此是最后一个using语句)

2) “POCO”对象的类型和基类型似乎不一致——这取决于您在应用程序中调用此函数的位置(单元测试项目与从MVP应用程序的presenter对象调用),这会导致我用粗体显示的行出现问题,因为如果它获取了错误的类型,则属性将不在其上,见下一行

从presenter对象中,.GetType显示为: 类名_96D74E07A154AE7BD32624F3B5D38E7F50333608A89B561218F854513E3B746 …位于System.Data.Entity.DynamicProxies命名空间中

这就是代码在该行上显示.GetType().BaseType的原因,它给出了: 类名 …在Company.Application.Project.Module中


但是在单元测试中,.GetType()显示为 Company.Application.Project.Module中的类名

BaseType显示为 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));
            }

        }
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
运用系统反思;
使用Company.Application.Project.Module;
名称空间Company.Application.Project
{
公共静态类排序器
{
公共静态IOrderedQueryable排序(此IQueryable源,字符串propertyName,布尔递减)
{
//如果清单上没有任何东西,就出手吧
if(source==null)
{
返回null;
}
if(source.Count()==0)
{
返回源作为IOrderedQueryable;
}
//获取类型--还是应该是基类型?没有人知道!
类型sourceType=source.First().GetType().BaseType;
//如果我们在上面一行找到了正确的类型,这就行了
PropertyInfo属性=sourceType.GetProperty(propertyName);
如果(降序)
{
返回source.OrderByDescending(e=>property.GetValue(e,null));
}
其他的
{
返回source.OrderBy(e=>property.GetValue(e,null));
}
}
}
}

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

public static IOrderedQueryable<T> Sort<T>(this IQueryable<T> source, string propertyName, bool descending) where T : MyBase
publicstaticiorderedqueryable排序(此IQueryable源代码,字符串propertyName,bool降序),其中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));
公共静态IOrderedQueryable排序(此IQueryable源、Func-sortExpr、bool-descending)
...
source.OrderBy(e=>sortExpr(e));

在我看来,根本没有理由这么做。您在函数中使用的是
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);
}
好了,就这样

我承认,
gvOrders\u排序
方法确实很混乱,但这就是在
GridView
中使用自定义排序的本质。它可以进一步封装,但我决定保持简单。(实际上,我开始着手创建一个助手类来处理排序和分页,但是.NET 4.0中的自定义分页比自定义排序更糟糕!)

希望你喜欢

[更正]


我刚刚注意到编写的代码有一个问题<代码>我的客户。订单将是一个
列表
而不是
IQueryable
。因此,要么将
sortKeys
更改为
Dictionary
,要么将调用更改为
MyCustomer.Orders.AsQueryable().OrderBy(sortKeys[e.SortExpression])
MyDbContext.Orders.Where(o=>o.Customer==MyCustomer.OrderBy(sortKeys[e.SortExpression])
。我把选择权留给你

我建议您使用.Net。您可以使用
ObjectContext.GetObjectType
获取代理的实际底层类型。如果不需要代理,只需关闭代理即可
DbContext.Configuration.ProxyCreationEnabled=false。或者像上面那样获取底层类型。只是出于好奇,您是否尝试过
typesourcetype=typeof(T)?>但在单元测试中,.GetType()在Company.Application.Project.Module中显示为类名。让我猜,在你的真实代码中,你正在使用一些智能ORM映射器;但是在您的单元测试中,您使用了一些假的存储库?这可能是不一致的原因,因为ORM映射器在
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; }
}
<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();
}