Rad Grid 的自定义分页和级联过滤





5.00/5 (1投票)
RadGrid 自定义分页和级联筛选的设计与实现。
您需要下载所有必要的包和引用才能构建此源代码。
引言
本文档解释了 自定义分页 的方法和步骤,遇到的依赖功能问题,以及 RadGrid 类似 Excel 的级联筛选 的实现。
背景
最近,我在一个项目中工作,其中 RadGrid 用于从大型数据集(超过 3.2 亿行)中显示数据,并实现类似 Excel 的级联筛选。
RadGrid 拥有非常强大的功能集,并且其中大部分功能无需编写任何代码即可使用。
对于小数据集(例如 200 条记录),它都能完美运行,但随着数据集大小的增加,应用程序或 RadGrid 的性能会下降。
这促使我们更改功能的默认实现,以应对大型数据集。
不出所料,RadGrid 支持自定义其大多数功能。一个可能的问题是,某个功能的自定义可能会影响一个或多个依赖功能。
它并不像看起来那么难,文章的其余部分将讨论实现过程 [附带了示例项目,“AdventureWorks2012”数据库用于演示]。
使用的工具/框架
- Visual Studio 2015
- 目标框架 4.5.2
- Entity Framework 6.1.3
- Telerik 版本 2013.3.1114.45
- Auto Mapper 5.1.1
- SQL Server 2012, AdventureWorks2012
分页
让我们从自定义分页开始,下面是 aspx 页面所需的更改。
- 设置
AllowPaging = true, AllowCustomPaging = true
--> 指示 RadGrid 不使用内置分页逻辑。 - 设置
VirtualItemCount = 100, Page size = 10
--> 也在代码后台动态设置这些属性。 - 为
OnNeedDatasource
事件设置事件处理程序 [CustomGrid_NeedDataSource
]。
<telerik:RadGrid runat="server" ID="CustomGrid"
AllowPaging="true" AllowCustomPaging="true" PageSize="10" VirtualItemCount="1000"
OnItemDataBound="CustomGrid_ItemDataBound" AutoGenerateColumns="False" CellSpacing="0"
GridLines="None">
在代码后台,在 OnNeedDatasource 事件处理程序中添加以下代码来加载数据集并将其分配给网格。
protected void CustomGrid_NeedDataSource(object sender, Telerik.Web.UI.GridNeedDataSourceEventArgs e)
{
var customers = CustomerService.GetCustomerDetail(Filter, CustomGrid.CurrentPageIndex,
CustomGrid.PageSize);
var totalRow = customers.Count() > 0 ? customers.First().TotalRow : 0;
CustomGrid.DataSource = customers;
CustomGrid.VirtualItemCount = totalRow;
}
函数中的前两行加载结果集和总行数。
CustomGrid.DataSource = customers;
--> 分配并绑定数据集到网格,在使用 OnNeedDatasource 处理程序时,无需显式调用 DataBind()。CustomGrid.VirtualItemCount = totalRow;
--> 设置查询返回的总行数,RadGrid 使用此值来计算实际页数。
让我们看看存储过程中 分页逻辑。
CREATE PROCEDURE USP_GetCustomerDetail
@FirstName Varchar(50) = NULL,
@LastName Varchar(50) = NULL,
@EmailAddress Varchar(250) = NULL,
@City Varchar(50) = NULL,
@StateProvinceName Varchar(50) = NULL,
@PostalCode Varchar(50) = NULL,
@CountryRegionName Varchar(50) = NULL,
@PageNumber Int = 1,
@PageSize Int = 10
AS
BEGIN
SET NOCOUNT ON;
--Validate pagination parameters
IF(@PageNumber IS NULL Or @PageNumber <= 0) SET @PageNumber = 1
IF(@PageSize IS NULL Or @PageSize < 0) SET @PageSize = 10
--Calculate start and end row to return
Declare @StartRow Int = ((@PageNumber - 1) * @PageSize) + 1
Declare @EndRow Int = @PageNumber * @PageSize
--Validate filter values
IF (@FirstName = '' OR @FirstName = 'All' OR @FirstName = 'NULL') SET @FirstName = NULL;
IF (@LastName = '' OR @LastName = 'All' OR @LastName = 'NULL') SET @LastName = NULL;
IF (@EmailAddress = '' OR @EmailAddress = 'All' OR @EmailAddress = 'NULL') SET @EmailAddress =
NULL;
IF (@City = '' OR @City = 'All' OR @City = 'NULL') SET @City = NULL;
IF (@StateProvinceName = '' OR @StateProvinceName = 'All' OR @StateProvinceName = 'NULL') SET
@StateProvinceName = NULL;
IF (@PostalCode = '' OR @PostalCode = 'All' OR @PostalCode = 'NULL') SET @PostalCode = NULL;
IF (@CountryRegionName = '' OR @CountryRegionName = 'All' OR @CountryRegionName = 'NULL')
SET @CountryRegionName = NULL;
SELECT FirstName, LastName, EmailAddress, PhoneNumber, EmailPromotion, City,
StateProvinceName, PostalCode, CountryRegionName, TotalRow
FROM
(
SELECT FirstName, LastName, EmailAddress, PhoneNumber, EmailPromotion, City,
StateProvinceName, PostalCode, CountryRegionName
, ROW_NUMBER() OVER(Order By FirstName ASC, LastName ASC) RowNumber, COUNT(1)
OVER() TotalRow
FROM [AdventureWorks2012].[Sales].[vIndividualCustomer]
WHERE (@FirstName IS NULL OR @FirstName = FirstName)
And (@LastName IS NULL OR @LastName = LastName)
And (@EmailAddress IS NULL OR @EmailAddress = EmailAddress)
And (@City IS NULL OR @City = City)
And (@StateProvinceName IS NULL OR @StateProvinceName = StateProvinceName)
And (@PostalCode IS NULL OR @PostalCode = PostalCode)
And (@CountryRegionName IS NULL OR @CountryRegionName = CountryRegionName)
) innerTable
WHERE RowNumber BETWEEN @StartRow And @EndRow
ORDER BY FirstName, LastName
END
GO
顶部段的参数用于传递当前选定的筛选值,最后两个参数用于传递分页所需的值。
ROW_NUMBER
函数用于计算行在结果集中的位置。记录总数使用 COUNT
函数计算。
结果集有一个单独的 TotalRow 列,这是避免运行单独的 SQL 语句来获取总行数的解决方法。
一切就绪,运行自定义分页网格将显示如下所示的网格。
分页按预期完美运行,此方法也可用于 Asp.Net GridView,只需稍作修改。
过滤
让我们开启 默认筛选 功能,看看它是如何工作的!
在主表视图中设置 AllowFilteringByColumn = true
,这会为所有列启用内置筛选功能。
上面的屏幕截图显示了带有默认筛选功能的 RadGrid,让我们讨论其中的问题!
如果我们尝试按当前显示页面中不存在的值进行筛选,网格将显示无结果。
这是因为内置筛选逻辑仅在当前页面中搜索项目,而不是整个表/结果集。
理想情况下,用户期望能够对整个表应用筛选,而不仅仅是当前页面,这导致也需要为筛选进行自定义实现。
预先在选择列表中加载 可能的筛选值 并使筛选器级联,将使网格更易用和高效。
可能的筛选值是指,整个结果集中某列的唯一值,而不限于页面上显示的内容。
示例:假设一个网格有两个筛选器“州”和“城市”,当用户在“州”筛选器中选择“NY”时,“城市”筛选器应该只包含属于“NY”州的城市,而不管当前选定的页码或页面大小。
让我们开始自定义筛选!
在需要筛选的列上设置 筛选模板。
--Textbox filter
<telerik:GridBoundColumn HeaderText="First Name" uniqueName="colFirstName" DataField="FirstName"
DataType="System.String">
<FilterTemplate>
<telerik:RadTextBox ID="filterFirstName" runat="server" Style="width: auto !important;"
data-filtercolumn="colFirstName" ClientEvents-OnValueChanged="textChanged">
</telerik:RadTextBox>
</FilterTemplate>
</telerik:GridBoundColumn>
--Select box filter
<telerik:GridBoundColumn HeaderText="City" uniqueName="colCity" DataField="City"
DataType="System.String" AllowFiltering="true">
<FilterTemplate>
<telerik:RadComboBox ID="filterCity" runat="server" Style="width: auto !important;"
AppendDataBoundItems="true"
data-filtercolumn="colCity" OnClientSelectedIndexChanged="filterChanged" >
<Items>
<telerik:RadComboBoxItem Text="All" />
</Items>
</telerik:RadComboBox>
</FilterTemplate>
</telerik:GridBoundColumn>
以上代码中设置的重要属性如下:
data-filtercolumn
--> 这个自定义属性(HTML 5 功能)用于存储筛选列的唯一名称。OnClientSelectedIndexChanged
--> 用于指定当 下拉框筛选器 中的筛选值更改时要调用的客户端函数名称。ClientEvents-OnValueChanged
--> 用于指定当 文本框筛选器 中的筛选值更改时要调用的客户端函数名称。
在触发 带选定筛选值的回发 的 JavaScript 函数中调用 RadGrid 筛选函数 tableView.filter(uniqueName, filterValue, filterType)
。
<script type="text/javascript">
function filterChanged(sender, args) {
var tableView = $find('<%=CustomGrid.ClientID %>').get_masterTableView();
var filterColumn = sender.get_element().getAttribute('data-filtercolumn');
tableView.filter(filterColumn, args.get_item().get_value(), "EqualTo");
}
function textChanged(sender, args) {
var tableView = $find('<%=CustomGrid.ClientID %>').get_masterTableView();
var filterColumn = sender.get_element().getAttribute('data-filtercolumn');
tableView.filter(filterColumn, sender._text, "EqualTo");
}
</script>
为 OnItemDataBound
事件分配一个处理程序 CustomGrid_ItemDataBound
,该事件在行添加到 RadGrid 时触发。
<telerik:RadGrid runat="server" ID="CustomGrid" AllowFilteringByColumn="true" MasterTableView-
AllowFilteringByColumn="true"
AllowPaging="true" AllowCustomPaging="true" PageSize="10" VirtualItemCount="1000"
GroupingSettings-CaseSensitive="false"
OnNeedDataSource="CustomGrid_NeedDataSource" OnItemDataBound="CustomGrid_ItemDataBound"
AutoGenerateColumns="False" CellSpacing="0" GridLines="None">
下面是用于选择可能筛选值的存储过程。
CREATE PROCEDURE USP_GetCustomerDetailFilterValues
@FirstName Varchar(50) = NULL,
@LastName Varchar(50) = NULL,
@EmailAddress Varchar(250) = NULL,
@City Varchar(50) = NULL,
@StateProvinceName Varchar(50) = NULL,
@PostalCode Varchar(50) = NULL,
@CountryRegionName Varchar(50) = NULL
AS
BEGIN
SET NOCOUNT ON;
--Validate filter values
IF (@FirstName = '' OR @FirstName = 'All' OR @FirstName = 'NULL') SET @FirstName = NULL;
IF (@LastName = '' OR @LastName = 'All' OR @LastName = 'NULL') SET @LastName = NULL;
IF (@EmailAddress = '' OR @EmailAddress = 'All' OR @EmailAddress = 'NULL') SET @EmailAddress =
NULL;
IF (@City = '' OR @City = 'All' OR @City = 'NULL') SET @City = NULL;
IF (@StateProvinceName = '' OR @StateProvinceName = 'All' OR @StateProvinceName = 'NULL') SET
@StateProvinceName = NULL;
IF (@PostalCode = '' OR @PostalCode = 'All' OR @PostalCode = 'NULL') SET @PostalCode = NULL;
IF (@CountryRegionName = '' OR @CountryRegionName = 'All' OR @CountryRegionName = 'NULL')
SET @CountryRegionName = NULL;
SELECT Distinct City, StateProvinceName, PostalCode, CountryRegionName
FROM [AdventureWorks2012].[Sales].[vIndividualCustomer]
WHERE (@FirstName IS NULL OR @FirstName = FirstName)
And (@LastName IS NULL OR @LastName = LastName)
And (@EmailAddress IS NULL OR @EmailAddress = EmailAddress)
And (@City IS NULL OR @City = City)
And (@StateProvinceName IS NULL OR @StateProvinceName = StateProvinceName)
And (@PostalCode IS NULL OR @PostalCode = PostalCode)
And (@CountryRegionName IS NULL OR @CountryRegionName = CountryRegionName)
END
GO
在代码后台,CustomGrid_OnItemDataBound
函数包含用于将筛选值加载并绑定到筛选模板中控件的代码。
属性 Filter
返回一个包含所有当前选定筛选值的筛选器对象。在筛选器下拉列表中选择“All”将将筛选值重置为 null(无)。
GetCustomterDetailFilter()
函数返回一个对象,该对象具有基于网格当前应用的筛选条件的可能筛选值。
(第二次数据库调用可能对某些情况很昂贵,如果那样,请使用 DataReader 在单次调用中读取多个结果集。
我使用了这种方法(两次数据库调用),因为 Entity Framework 6 对于读取多个结果的支持不足,而且它并未对性能造成任何影响。)
完成了以上所有更改后,代码后台将如下所示,分页和筛选应该可以正常工作。
using System;
using System.Linq;
using System.Web.UI;
using RadGridCustomPaginationAndCustomFilter.BusinessLogic;
using RadGridCustomPaginationAndCustomFilter.Model;
using Telerik.Web.UI;
namespace RadGridCustomPaginationAndCustomFilter
{
public partial class _Default : Page
{
private CustomerDetailService CustomerService = new CustomerDetailService();
#region Uninque Name for Custom Grid Columns
private string colFirstName = "colFirstName";
private string colLastName = "colLastName";
private string colEmailAddress = "colEmailAddress";
private string colCity = "colCity";
private string colStateProvinceName = "colStateProvinceName";
private string colPostalCode = "colPostalCode";
private string colCountryRegionName = "colCountryRegionName";
#endregion
protected void Page_Load(object sender, EventArgs e)
{
}
protected void CustomGrid_NeedDataSource(object sender,
Telerik.Web.UI.GridNeedDataSourceEventArgs e)
{
var customers = CustomerService.GetCustomerDetail(Filter, CustomGrid.CurrentPageIndex,
CustomGrid.PageSize);
var totalRow = customers.Count() > 0 ? customers.First().TotalRow : 0;
CustomGrid.DataSource = customers;
CustomGrid.VirtualItemCount = totalRow;
}
protected void CustomGrid_ItemDataBound(object sender, Telerik.Web.UI.GridItemEventArgs e)
{
if (e.Item.ItemType == GridItemType.FilteringItem)
{
LoadFilterValues(e);
}
}
private CustomerDetailFilter Filter
{
get
{
return new CustomerDetailFilter
{
FirstName = CustomGrid.MasterTableView.GetColumn(colFirstName).CurrentFilterValue,
LastName = CustomGrid.MasterTableView.GetColumn(colLastName).CurrentFilterValue,
EmailAddress = CustomGrid.MasterTableView.GetColumn(colEmailAddress).CurrentFilterValue,
City = CustomGrid.MasterTableView.GetColumn(colCity).CurrentFilterValue,
PostalCode = CustomGrid.MasterTableView.GetColumn(colPostalCode).CurrentFilterValue,
StateProvinceName = CustomGrid.MasterTableView.GetColumn
(colStateProvinceName).CurrentFilterValue,
CountryRegionName = CustomGrid.MasterTableView.GetColumn
(colCountryRegionName).CurrentFilterValue
};
}
}
private void LoadFilterValues(GridItemEventArgs arg)
{
var result = CustomerService.GetCustomerDetailFilterValues(Filter);
var filterFirstName = arg.Item.FindControl("filterFirstName") as RadTextBox;
var filterLastName = arg.Item.FindControl("filterLastName") as RadTextBox;
var filterEmailAddress = arg.Item.FindControl("filterEmailAddress") as RadTextBox;
filterFirstName.Text = Filter.FirstName;
filterLastName.Text = Filter.LastName;
filterEmailAddress.Text = Filter.EmailAddress;
var filterCity = arg.Item.FindControl("filterCity") as RadComboBox;
var filterStateProvinceName = arg.Item.FindControl("filterStateProvinceName") as RadComboBox;
var filterPostalCode = arg.Item.FindControl("filterPostalCode") as RadComboBox;
var filterCountryRegionName = arg.Item.FindControl("filterCountryRegionName") as RadComboBox;
filterCity.DataSource = result.City;
filterCity.DataBind();
filterCity.SelectedValue = Filter.City;
filterStateProvinceName.DataSource = result.StateProvinceName;
filterStateProvinceName.DataBind();
filterStateProvinceName.SelectedValue = Filter.StateProvinceName;
filterPostalCode.DataSource = result.PostalCode;
filterPostalCode.DataBind();
filterPostalCode.SelectedValue = Filter.PostalCode;
filterCountryRegionName.DataSource = result.CountryRegionName;
filterCountryRegionName.DataBind();
filterCountryRegionName.SelectedValue = Filter.CountryRegionName;
}
}
}
下面是 RadGrid 带有自定义分页和自定义筛选的屏幕截图。
结论
这是最好的方法吗?是的,对于我之前的项目!这取决于数据集大小、索引、应用程序设计/架构以及用户行为/期望等因素。
在我的案例中,这种方法在处理大型数据集的自定义分页和级联筛选方面效果非常好。如果有其他更好的方法,请分享!