我们正在使用Entity Framework和一个“POCO”生成器来创建我们的类型,这些类型被传递到各个层,它们属于(更改为保护无辜)命名空间Company.Application.Project.Module。这些POCO对象都继承自一个基类,它为我们处理一些基本的东西。
我想编写一个函数,可以收集这些对象并按属性名对它们进行排序。
我写了以下函数 - 这是我想要做的事情的要点,但我不喜欢它有几个原因:
1)这不适用于任何对象类型,它必须是SortHelper类知道的对象类型(因此是最后一个using语句)。
2)“POCO”对象的Type和BaseType似乎不一致 - 取决于你在app中调用此函数的位置(单元测试项目与我们的MVP应用程序的presenter对象调用)会导致问题我用粗线加了一条线,因为如果它抓住了错误的类型,那么该属性将不会出现在它上面,在下一行中。
从演示者对象中,.GetType显示为: ClassName_96D74E07A154AE7BDD32624F3B5D38E7F50333608A89B561218F854513E3B746 ...在System.Data.Entity.DynamicProxies命名空间内。
这就是为什么代码在那行上说.GetType()。BaseType,它给了我: 班级名称 ...在Company.Application.Project.Module
中但在单元测试中,.GetType()显示为 Company.Application.Project.Module中的ClassName
和BaseType显示为 Company.Application.Project.Module中的BaseClass
......这更有意义,但我不明白这种不一致 - 而且不一致让我感到害怕。
3)讨厌使用Reflection来做这件事。
如果有人有更好的方法,或者甚至修复使反射与命名空间/类型一起运行 - 我当然会很感激!
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));
}
}
}
}
答案 0 :(得分:3)
在我看来,完全没有理由这样做。您在函数中使用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])
。我把选择留给你。 ;)
答案 1 :(得分:2)
如果它们都从基类继承,为什么不约束泛型参数?
public static IOrderedQueryable<T> Sort<T>(this IQueryable<T> source, string propertyName, bool descending) where T : MyBase
这样,编译器不会让将类传递给不从MyBase
继承的函数,而你是不是“没人知道?!”领土:)
答案 2 :(得分:1)
只需预测要分类的属性:
public static IOrderedQueryable<T> Sort<T, U>(this IQueryable<T> source, Func<T, U> sortExpr, bool descending)
...
source.OrderBy(e => sortExpr(e));