代码改变世界

Linq数据源, GridView排序

2010-11-23 11:49  Jeff Chow  阅读(966)  评论(3编辑  收藏  举报

最近迷上了Linq,在做ASP.NET的项目时,把原来的DataTab的数据源也换成了以Linq的方式获取的数据源,即IEnumerable<T>的一个实例。将其绑定到GridView的时候,遇到了排序方面的问题。折腾了一大圈才搞明白.NET 3.5中委托的写法,问题得以解决。

 

先是Google了一下GridView如何实现IEnumerable<T>数据源的排序,得到如下的解决方案:

protected void GridView_Sorting(object sender, GridViewSortEventArgs e)
{
    if (e.SortDirection == SortDirection.Ascending)
    {
        switch (e.SortExpression)
        {
            case "property1":
                //IEnumber<T>.OrderBy(t => t.property1);
                break;
            case "property2":
                //IEnumber<T>.OrderBy(t => t.property2);
                break;
            default:
                break;
        }
    }
    else
    {
        switch (e.SortExpression)
        {
            case "property1":
                //IEnumber<T>.OrderByDescending(t => t.property1);
                break;
            case "property2":
                //IEnumber<T>.OrderByDescending(t => t.property2);
                break;
            default:
                break;
        }
    }
}

 

显然这种方案,类型排序的属性多了的话会写死人的。

 

然后又参考了《Gridview使用LINQ与ObjectDataSource实现自动分页和排序》这篇文章,自己看文章不细心,一开始没明白,博主回复后才清楚可以通过添加对System.Linq.Dynamic的引用,直接使用IQueryable<T>.OrderBy(string sortExpression)函数。然后生成,运行,抛异常,具体的异常名字忘记了,反正就是无法动态地创建查询。

 

抛出这个异常的原因是,上面文章中的作者,IQueryable<T>中的T是Linq to Sql直接生成的模型,而我的项目中,这个模型T并不是直接对应数据库的数据表。我的数据库里边有Tab_DeviceInfo(设备信息表)和Tab_DeviceStatus(设备状态表),但是我的程序代码里边只定义了一个Device类来存放设备的信息和设备的状态。

 

继续搜索,看到这篇文章,由于本人英文比较搓,其实很多的说明没看懂,只看了代码部分。作者通过反射构造Expression的实例作为参数调用IQueryable<T>.OrderBy(Expression sortExpression)函数实现排序。由此,我想到通过反射去构造IEnumerable<T>.OrderBy(Func key)的参数,并调用它。

 

首先,Func是个delegate。通过参考《从.NET框架中委托写法的演变谈开去(中)》我搞明白了.NET 3.5中委托的写法,最后得出关于排序的解决办法就是:

string sortExpression;
SortDirection sortDirection;
IEnumerable<Device> source;

//set value.

if (sortDirection == SortDirection.Ascending)
{
    GridView1.DataSource = source.OrderBy(item => item.GetType().GetProperty(sortExpression).GetValue(item, null));
}
else
{
    GridView1.DataSource = source.OrderByDescending(item => item.GetType().GetProperty(sortExpression).GetValue(item, null));
}

 

很怪异,不过我试过了可以实现。前面说了一大堆,其实都只是我解决这个问题的过程而已。不过,可以用于实现泛型为Linq to Sql自动生成的类型的排序。下面奉上完整的例子代码。

 

Device类:

public class Device
{
    private Tab_DeviceInfo _deviceInfo;

    /// <summary>
    /// 构造器。
    /// </summary>
    /// <param name="deviceInfo"></param>
    internal Device(Tab_DeviceInfo deviceInfo)
    {
        _deviceInfo = deviceInfo;

        //其他信息的赋值,如果有的话。
    }

    #region 公开属性。

    /// <summary>
    /// 数据主键。
    /// </summary>
    public int ID
    {
        get
        {
            return _deviceInfo.DI_ID;
        }
        set
        {
            _deviceInfo.DI_ID = value;
        }
    }

    /// <summary>
    /// 设备编号。
    /// </summary>
    public string NO
    {
        get
        {
            return _deviceInfo.DI_DeviceNO;
        }
        set
        {
            _deviceInfo.DI_DeviceNO = value;
        }
    }

    #endregion
}

 

.aspx文件:

<form id="form1" runat="server">
<div>
    <asp:GridView ID="GridViewDevice" runat="server" AllowSorting="True" OnSorting="GridViewDevice_Sorting">
        <Columns>
            <asp:BoundField DataField="ID" HeaderText="数据主键" SortExpression="ID" />
            <asp:BoundField DataField="NO" HeaderText="设备编号" SortExpression="NO" />
        </Columns>
    </asp:GridView>
</div>
</form>

 

.aspx.cs文件:

public partial class OrderByProperty : System.Web.UI.Page
{
    private GSMDataContext _data;

    /// <summary>
    /// 构造器。
    /// </summary>
    public OrderByProperty()
    {
        _data = new GSMDataContext();
    }

    private void bindGridViewData()
    {
        if (!IsPostBack)
        {
            GridViewDevice.DataKeyNames = new string[] { "ID" };
        }

        string sortExpression = GridViewDevice.Attributes["sortExpression"];
        SortDirection sortDirection = GridViewDevice.Attributes["sortDirection"] == "ASC" ? SortDirection.Ascending : SortDirection.Descending;
        IEnumerable<Device> source = _data.Tab_DeviceInfos.Select(info => new Device(info));

        if (string.IsNullOrEmpty(sortExpression))
        {
            GridViewDevice.DataSource = source;
        }
        else
        {
            if (sortDirection == SortDirection.Ascending)
            {
                GridViewDevice.DataSource = source.OrderBy(item => item.GetType().GetProperty(sortExpression).GetValue(item, null));
            }
            else
            {
                GridViewDevice.DataSource = source.OrderByDescending(item => item.GetType().GetProperty(sortExpression).GetValue(item, null));
            }
        }
        GridViewDevice.DataBind();
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        bindGridViewData();
    }

    protected void GridViewDevice_Sorting(object sender, GridViewSortEventArgs e)
    {
        //保存sortExpression和sortDirection。
        string sortExpression = e.SortExpression;
        string sortDirection = "ASC";
        if (sortExpression.Equals(GridViewDevice.Attributes["sortExpression"]) && "ASC".Equals(GridViewDevice.Attributes["sortDirection"]))
        {
            sortDirection = "DESC";
        }
        GridViewDevice.Attributes.Add("sortExpression", sortExpression);
        GridViewDevice.Attributes.Add("sortDirection", sortDirection);

        bindGridViewData();
    }
}

 

 

补充:

文章是昨天写完的,在IEnumerable<Device> source = _data.Tab_DeviceInfos.Select(info => new Device(info));这行代码上面,昨天用的是var source = _data.Tab_DeviceInfos.Select(info => new Device(info));。发布了以后,点了下运行,抛了异常,不能根据System.Object类型进行排序。不直接声明为IEnumber<T>的话,Select函数返回的是IQueryable<T>的实例,则会抛出上述的异常。解决方法的话,可以像我这样将其声明为IEnumberable<T>,或者先调用source.AsEnumerable(),又或者用反射获取其类型,然后进行强转。代码如下:

工具类SortUtility.cs:

public class SortUtility
{
    public static object Sort(IEnumerable source, string sortExpression, SortDirection sortDirection)
    {
        IEnumerator sourceEnumerator = source.GetEnumerator();

        if (!sourceEnumerator.MoveNext())
        {
            return null;
        }

        Type dataSourceType = source.GetType();

        Type dataItemType = typeof(object);

        if (dataSourceType.HasElementType)
        {
            dataItemType = dataSourceType.GetElementType();
        }
        else if (dataSourceType.IsGenericType)
        {
            dataItemType = dataSourceType.GetGenericArguments().FirstOrDefault();
        }
        else
        {
            if (sourceEnumerator.Current != null)
            {
                dataItemType = sourceEnumerator.Current.GetType();
            }
        }

        // We'll handle things like LINQ to SQL differently by passing the love
        // on to the provider.
        PropertyInfo sortProperty = dataItemType.GetProperty(sortExpression);

        //找不到排序属性,返回原数据。
        if (sortProperty == null)
        {
            return source;
        }

        Type sorterType = typeof(SortUtility<,>).MakeGenericType(dataItemType, sortProperty.PropertyType);

        object sorterObject = Activator.CreateInstance(sorterType);

        return sorterType.GetMethod("Sort", new Type[] { dataSourceType, typeof(string), typeof(SortDirection) })
            .Invoke(sorterObject, new object[] { source, sortExpression, sortDirection });
    }
}

public class SortUtility<T, ST>
{
    public static IEnumerable<T> Sort(IEnumerable<T> source, string sortExpression, SortDirection sortDirection)
    {
        Func<T, ST> sortFunc = item => (ST)typeof(T).GetProperty(sortExpression).GetValue(item, null);

        if (sortDirection == SortDirection.Ascending)
        {
            return source.OrderBy<T, ST>(sortFunc);
        }
        else
        {
            return source.OrderByDescending<T, ST>(sortFunc);
        }
    }
}

 

.aspx.cs中绑定数据部分的代码:

private void bindGridViewData()
{
    if (!IsPostBack)
    {
        GridViewDevice.DataKeyNames = new string[] { "ID" };
    }

    string sortExpression = GridViewDevice.Attributes["sortExpression"];
    SortDirection sortDirection = GridViewDevice.Attributes["sortDirection"] == "ASC" ? SortDirection.Ascending : SortDirection.Descending;
    var source = _data.Tab_DeviceInfos.Select(info => new Device(info));

    if (string.IsNullOrEmpty(sortExpression))
    {
        GridViewDevice.DataSource = source;
    }
    else
    {
        /*
        if (sortDirection == SortDirection.Ascending)
        {
            GridViewDevice.DataSource = source.OrderBy(item => item.GetType().GetProperty(sortExpression).GetValue(item, null));
        }
        else
        {
            GridViewDevice.DataSource = source.OrderByDescending(item => item.GetType().GetProperty(sortExpression).GetValue(item, null));
        }
         */
        GridViewDevice.DataSource = SortUtility.Sort(source, sortExpression, sortDirection);
    }
    GridViewDevice.DataBind();
}