65.9K
CodeProject 正在变化。 阅读更多。
Home

处理 GridView.OnSorting() 并使用 LINQ 动态创建排序表达式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (16投票s)

2011 年 8 月 28 日

CPOL

1分钟阅读

viewsIcon

106876

downloadIcon

983

如何使用 LINQ 表达式树从 GridViewSortEventArgs 创建排序表达式。

引言

ASP.NET 2.0 中的 GridView 控制是一个很棒且广泛使用的控件

<asp:GridView runat="server" ID="gridPersons" 
         AutoGenerateColumns="false" AllowSorting="true" 
         OnSorting="gridPersons_Sorting">
    <Columns>
        <asp:BoundField HeaderText="First name" 
           DataField="FirstName" SortExpression="FirstName" />
        <asp:BoundField HeaderText="Last name" 
           DataField="LastName" SortExpression="LastName" />
    </Columns>
</asp:GridView>
protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
    string sortExpression = e.SortExpression;
}

但有一个显著的限制:排序事件参数 GridViewSortEventArg 基于标记中的字符串值。因此,它不能直接用于使用 LINQ 进行编程排序。

本文档描述了如何将字符串参数转换为排序表达式。

背景

假设我们有一个强类型类的序列,例如 Person,并且我们想将其绑定到 GridView,然后通过单击相应的列对其进行排序

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

IEnumerable<Person> persons = GetPersons();
gridPersons.DataSource =  persons.ToArray();
gridPersons.DataBind();

通常,我们可以硬编码排序表达式(即,要排序的属性)和方向(升序、降序)。

protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
    Func<Person, object> prop = null;
    switch (e.SortExpression)
    {
        case "FirstName":
        {
            prop = p => p.FirstName;
            break;
        }
        case "LastName":
        {
            prop = p => p.LastName;
            break;
        }
    }

    Func<IEnumerable<Person>, Func<Person, object>, IEnumerable<Person>> func = null;

    switch (e.SortDirection)
    {
        case SortDirection.Ascending:
        {
            func = (c, p) => c.OrderBy(p);
            break;
        }
        case SortDirection.Descending:
        {
            func = (c, p) => c.OrderByDescending(p);
            break;
        }
    }

    IEnumerable<Person> persons = GetPersons();
    persons = func(persons, prop);

    gridPersons.DataSource = persons.ToArray();
    gridPersons.DataBind();
}

但这种方法存在一些缺点:所有对象的属性以及排序方向都被硬编码。更好的方法是动态创建所有这些。

解决方案

让我们将逻辑封装到一些专门的类中。

第一个将 SortDirection 转换为 Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>>,这是一个接受序列和另一个接受对象并返回其属性之一的函数;并返回按该对象属性排序的序列。

public static class SortExpressionConverter<T>
{
    private static IDictionary<SortDirection, ISortExpression> expressions = 
        new Dictionary<SortDirection, ISortExpression>
    {
        { SortDirection.Ascending, new OrderByAscendingSortExpression() },
        { SortDirection.Descending, new OrderByDescendingSortExpression() }
    };

    interface ISortExpression
    {
        Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> GetExpression();
    }

    class OrderByAscendingSortExpression : ISortExpression
    {
        public Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> GetExpression()
        {
            return (c, f) => c.OrderBy(f);
        }
    }

    class OrderByDescendingSortExpression : ISortExpression
    {
        public Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> GetExpression()
        {
            return (c, f) => c.OrderByDescending(f);
        }
    }

    public static Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>>
        Convert(SortDirection direction)
    {
        ISortExpression sortExpression = expressions[direction];
        return sortExpression.GetExpression();
    }
}

第二个构建一个 lambda 表达式 Expression<Func<T, object>> 并返回基础 lambda Func<T, object>

public static class SortLambdaBuilder<T>
{
    public static Func<T, object> Build(string columnName, SortDirection direction)
    {
        // x
        ParameterExpression param = Expression.Parameter(typeof(T), "x");

        // x.ColumnName1.ColumnName2
        Expression property = columnName.Split('.')
                                        .Aggregate<string, Expression>
                                        (param, (c, m) => Expression.Property(c, m));

        // x => x.ColumnName1.ColumnName2
        Expression<Func<T, object>> lambda = Expression.Lambda<Func<T, object>>(
            Expression.Convert(property, typeof(object)),
            param);
        
        Func<T, object> func = lambda.Compile();
        return func;
    }
}

将这些类的用法包装到扩展方法中

public static class CollectionExtensions
{
    public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> collection, 
           string columnName, SortDirection direction)
    {
        Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression =
            SortExpressionConverter<T>.Convert(direction);

        Func<T, object> lambda =
            SortLambdaBuilder<T>.Build(columnName, direction);

        IEnumerable<T> sorted = expression(collection, lambda);
        return sorted;
    }
}

所以现在我们有一个灵活、完全动态且通用的解决方案

protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
    IEnumerable<Person> persons = GetPersons();
    persons = persons.OrderBy(e.SortExpression, e.SortDirection);

    gridPersons.DataSource = persons.ToArray();
    gridPersons.DataBind();
}

兴趣点 

以下是一些与该主题相关的 StackOverflow 讨论

历史记录

  • 2011 年 8 月 27 日 - 初始发布
  • 2012 年 3 月 22 日 - 扩展了 OrderBy<T>() 扩展方法,使其更易于阅读和理解
  • 2014 年 7 月 17 日 - SortLambdaBuilder<T> 支持嵌套属性
© . All rights reserved.