Эффективное разбиение на страницы больших объемов данных (C#)Efficiently Paging Through Large Amounts of Data (C#)

по Скотт Митчеллby Scott Mitchell

Скачивание примера приложения или Загрузка PDF-файлаDownload Sample App or Download PDF

Параметр разбиения по страницам по умолчанию элемента управления представления данных не подходит для работы с большими объемами данных, так как его базовый элемент управления источника данных получает все записи, даже если отображается только подмножество данных.The default paging option of a data presentation control is unsuitable when working with large amounts of data, as its underlying data source control retrieves all records, even though only a subset of data is displayed. В таких обстоятельствах необходимо включить пользовательское разбиение на страницы.In such circumstances, we must turn to custom paging.

ВведениеIntroduction

Как обсуждалось в предыдущем учебном курсе, разбиение по страницам можно реализовать одним из двух способов:As we discussed in the preceding tutorial, paging can be implemented in one of two ways:

  • Разбиение по страницам по умолчанию можно реализовать, просто установив флажок Включить разбиение по страницам в смарт-теге элемента управления данными. Однако при просмотре страницы данных ObjectDataSource извлекает все записи, несмотря на то, что на странице отображаются только их подмножество.Default Paging can be implemented by simply checking the Enable Paging option in the data Web control s smart tag; however, whenever viewing a page of data, the ObjectDataSource retrieves all of the records, even though only a subset of them are displayed in the page
  • Пользовательское разбиение на страницы улучшает производительность разбиения по страницам по умолчанию, получая из базы данных только те записи, которые должны отображаться для конкретной странице данных, запрошенных пользователем. Однако пользовательское разбиение по страницам требует немного больше усилий, чем разбиение по умолчаниюCustom Paging improves the performance of default paging by retrieving only those records from the database that need to be displayed for the particular page of data requested by the user; however, custom paging involves a bit more effort to implement than default paging

Из-за простоты реализации просто установите флажок и повторите попытку.Due to the ease of implementation just check a checkbox and you re done! разбиение по страницам по умолчанию является привлекательным вариантом.default paging is an attractive option. Его упрощенный подход к извлечению всех записей, тем не менее, делает его предполагающий выбором при разбиении на страницы достаточно большого объема данных или для сайтов с большим количеством одновременных пользователей.Its naive approach in retrieving all of the records, though, makes it an implausible choice when paging through sufficiently large amounts of data or for sites with many concurrent users. В таких обстоятельствах необходимо включить пользовательское разбиение на страницы, чтобы обеспечить скорость реагирования системы.In such circumstances, we must turn to custom paging in order to provide a responsive system.

Задача пользовательского разбиения по страницам — возможность написать запрос, возвращающий точный набор записей, необходимых для конкретной страницы данных.The challenge of custom paging is being able to write a query that returns the precise set of records needed for a particular page of data. К счастью, Microsoft SQL Server 2005 предоставляет новое ключевое слово для ранжирования результатов, что позволяет нам создавать запросы, которые эффективно извлекают подмножество записей.Fortunately, Microsoft SQL Server 2005 provides a new keyword for ranking results, which enables us to write a query that can efficiently retrieve the proper subset of records. В этом учебнике мы увидим, как использовать это новое ключевое слово SQL Server 2005 для реализации пользовательского разбиения на страницы в элементе управления GridView.In this tutorial we'll see how to use this new SQL Server 2005 keyword to implement custom paging in a GridView control. Несмотря на то, что пользовательский интерфейс для пользовательского разбиения по страницам аналогичен постраничному разбиению по умолчанию, переход от одной страницы к другой с помощью пользовательского разбиения по страницам может быть несколько порядков быстрее, чем разбиение по страницамWhile the user interface for custom paging is identical to that for default paging, stepping from one page to the next using custom paging can be several orders of magnitude faster than default paging.

Note

Точное увеличение производительности, которое проявляется с помощью пользовательского разбиения на страницы, зависит от общего числа страниц, на которых выполняется разбивка на страницы, и нагрузки на сервер базы данных.The exact performance gain exhibited by custom paging depends on the total number of records being paged through and the load being placed on the database server. По завершении работы с этим руководством мы рассмотрим некоторые приблизительные метрики, демонстрирующие преимущества производительности, получаемые с помощью пользовательского разбиения на страницы.At the end of this tutorial we'll look at some rough metrics that showcase the benefits in performance obtained through custom paging.

Шаг 1. Основные сведения о пользовательском процессе разбиения на страницыStep 1: Understanding the Custom Paging Process

При разбиении по страницам данных точные записи, отображаемые на странице, зависят от запрашиваемой страницы данных и количества записей, отображаемых на странице.When paging through data, the precise records displayed in a page depend upon the page of data being requested and the number of records displayed per page. Например, представьте, что нам нужно пролистать продукты 81, отображая 10 продуктов на странице.For example, imagine that we wanted to page through the 81 products, displaying 10 products per page. При просмотре первой страницы нам нужно, чтобы продукты с 1 по 10 были При просмотре второй страницы мы будем заинтересованы в продуктах с 11 по 20 и т. д.When viewing the first page, we d want products 1 through 10; when viewing the second page we d be interested in products 11 through 20, and so on.

Есть три переменные, которые определяют, какие записи необходимо получить, и как должен быть визуализирован интерфейс разбиения по страницам:There are three variables that dictate what records need to be retrieved and how the paging interface should be rendered:

  • Начать индекс строки индекс первой строки на странице отображаемых данных; Этот индекс можно вычислить, умножив индекс страницы по записям, отображаемым на страницу, и добавив ее.Start Row Index the index of the first row in the page of data to display; this index can be calculated by multiplying the page index by the records to display per page and adding one. Например, при разбиении по записям 10 за раз для первой страницы (с индексом страницы 0) Индекс начальной строки равен 0 * 10 + 1, или 1; для второй страницы (чей индекс страницы равен 1), Индекс начальной строки равен 1 * 10 + 1 или 11.For example, when paging through records 10 at a time, for the first page (whose page index is 0), the Start Row Index is 0 * 10 + 1, or 1; for the second page (whose page index is 1), the Start Row Index is 1 * 10 + 1, or 11.
  • Максимальное число строк , отображаемых на страницу по максимальному числу записей.Maximum Rows the maximum number of records to display per page. Эта переменная называется максимальным числом строк, так как для последней страницы может быть меньше записей, чем размер страницы.This variable is referred to as maximum rows since for the last page there may be fewer records returned than the page size. Например, при разбиении по страницам 10 записей продуктов 81 на странице девятая и последняя страницы будут содержать только одну запись.For example, when paging through the 81 products 10 records per page, the ninth and final page will have just one record. Ни одна страница не будет показывать больше записей, чем максимальное значение строк.No page, though, will show more records than the Maximum Rows value.
  • Общее количество записей общее число страниц, на которые размещается страница.Total Record Count the total number of records being paged through. Хотя эта переменная не требуется для определения того, какие записи следует получить для данной страницы, она определяет интерфейс разбиения на себя.While this variable isn t needed to determine what records to retrieve for a given page, it does dictate the paging interface. Например, если имеется 81 продуктов, то интерфейс разбиения по страницам будет отображать девять номеров страниц в пользовательском интерфейсе разбиения на страницы.For example, if there are 81 products being paged through, the paging interface knows to display nine page numbers in the paging UI.

При использовании разбиения по страницам по умолчанию индекс начальной строки вычисляются как произведение индекса страницы и размера страницы плюс один, в то время как максимальное количество строк — это просто размер страницы.With default paging, the Start Row Index is computed as the product of the page index and the page size plus one, whereas the Maximum Rows is simply the page size. Поскольку разбиение по страницам по умолчанию извлекает все записи из базы данных при отрисовке любой страницы данных, индекс каждой строки известен, поэтому перемещение в строку «начало индекса строки» является тривиальной задачей.Since default paging retrieves all of the records from the database when rendering any page of data, the index for each row is known, thereby making moving to Start Row Index row a trivial task. Кроме того, доступен общий счетчик записей, так как это просто количество записей в DataTable (или любого объекта, используемого для хранения результатов базы данных).Moreover, the Total Record Count is readily available, as it s simply the number of records in the DataTable (or whatever object is being used to hold the database results).

При наличии переменных "Индекс начальной строки" и "максимальное число строк" реализация пользовательского разбиения по страницам должна возвращать только точное подмножество записей, начиная с индекса начальной строки, и до максимального количества строк записей после этого.Given the Start Row Index and Maximum Rows variables, a custom paging implementation must only return the precise subset of records starting at the Start Row Index and up to Maximum Rows number of records after that. Пользовательское разбиение на страницы дает две проблемы:Custom paging provides two challenges:

  • Необходимо иметь возможность эффективно связать индекс строки с каждой строкой во всех данных, на которых выполняется разгрузка, чтобы можно было начать Возврат записей по указанному индексу начальной строки.We must be able to efficiently associate a row index with each row in the entire data being paged through so that we can start returning records at the specified Start Row Index
  • Необходимо указать общее количество записей, на которые размещается страницаWe need to provide the total number of records being paged through

В следующих двух шагах мы рассмотрим сценарий SQL, необходимый для ответа на эти две проблемы.In the next two steps we'll examine the SQL script needed to respond to these two challenges. Помимо скрипта SQL, нам также потребуется реализовать методы в DAL и BLL.In addition to the SQL script, we'll also need to implement methods in the DAL and BLL.

Шаг 2. возвращение общего количества записей, передаваемых по страницамStep 2: Returning the Total Number of Records Being Paged Through

Прежде чем исследовать, как получить точное подмножество записей для отображаемой страницы, давайте сначала посмотрим, как вернуть общее количество записей, на которые размещается страница.Before we examine how to retrieve the precise subset of records for the page being displayed, let s first look at how to return the total number of records being paged through. Эти сведения необходимы для правильной настройки интерфейса пользователя с разбиением на страницы.This information is needed in order to properly configure the paging user interface. Общее число записей, возвращаемых определенным запросом SQL, можно получить с помощью COUNT агрегатной функции.The total number of records returned by a particular SQL query can be obtained by using the COUNT aggregate function. Например, чтобы определить общее число записей в Products таблице, можно использовать следующий запрос:For example, to determine the total number of records in the Products table, we can use the following query:

SELECT COUNT(*)
FROM Products

Добавим в DAL метод, возвращающий эту информацию.Let s add a method to our DAL that returns this information. В частности, мы создадим метод DAL с именем TotalNumberOfProducts() , который выполняет приведенную SELECT выше инструкцию.In particular, we'll create a DAL method called TotalNumberOfProducts() that executes the SELECT statement shown above.

Начните с открытия Northwind.xsd файла типизированного набора данных в App_Code/DAL папке.Start by opening the Northwind.xsd Typed DataSet file in the App_Code/DAL folder. Затем щелкните правой кнопкой мыши ProductsTableAdapter в конструкторе и выберите команду Добавить запрос.Next, right-click on the ProductsTableAdapter in the Designer and choose Add Query. Как мы видели в предыдущих учебных курсах, это позволит нам добавить в DAL новый метод, который при вызове будет выполнять определенную инструкцию или хранимую процедуру SQL.As we ve seen in previous tutorials, this will allow us to add a new method to the DAL that, when invoked, will execute a particular SQL statement or stored procedure. Как и в случае с нашими методами TableAdapter в предыдущих руководствах, для этого нужно использовать специальный оператор SQL.As with our TableAdapter methods in previous tutorials, for this one opt to use an ad-hoc SQL statement.

Использование специального оператора SQL

Рис. 1. Использование специального оператора SQLFigure 1: Use an Ad-Hoc SQL Statement

На следующем экране можно указать тип создаваемого запроса.On the next screen we can specify what type of query to create. Поскольку этот запрос возвращает одиночное скалярное значение, общее число записей в Products таблице выберите, SELECT которое возвращает параметр с одним значением.Since this query will return a single, scalar value the total number of records in the Products table choose the SELECT which returns a singe value option.

Настройка запроса для использования инструкции SELECT, возвращающей одиночное значение

Рис. 2. Настройка запроса для использования инструкции SELECT, возвращающей одиночное значениеFigure 2: Configure the Query to Use a SELECT Statement that Returns a Single Value

После указания используемого типа запроса необходимо указать запрос.After indicating the type of query to use, we must next specify the query.

Использование запроса выбор числа (*) из продуктов

Рис. 3. Использование запроса SELECT COUNT ( * ) из продуктовFigure 3: Use the SELECT COUNT(*) FROM Products Query

Наконец, укажите имя метода.Finally, specify the name for the method. Как было упомянуто выше, давайте будем использовать TotalNumberOfProducts .As aforementioned, let s use TotalNumberOfProducts.

Назовите метод DAL Тоталнумберофпродуктс

Рис. 4. имя метода DAL тоталнумберофпродуктсFigure 4: Name the DAL Method TotalNumberOfProducts

После нажатия кнопки Готово мастер добавит TotalNumberOfProducts метод в DAL.After clicking Finish, the wizard will add the TotalNumberOfProducts method to the DAL. Скалярные методы, возвращающие DAL, возвращают типы, допускающие значение null, в случае, если результат запроса SQL равен NULL .The scalar returning methods in the DAL return nullable types, in case the result from the SQL query is NULL. COUNTОднако наш запрос всегда будет возвращать значение, отличное от NULL значения, независимо от того, что метод DAL возвращает целое число, допускающее значение null.Our COUNT query, however, will always return a non-NULL value; regardless, the DAL method returns a nullable integer.

В дополнение к методу DAL нам также нужен метод в BLL.In addition to the DAL method, we also need a method in the BLL. Откройте ProductsBLL файл класса и добавьте TotalNumberOfProducts метод, который просто вызывает метод DAL s TotalNumberOfProducts :Open the ProductsBLL class file and add a TotalNumberOfProducts method that simply calls down to the DAL s TotalNumberOfProducts method:

public int TotalNumberOfProducts()
{
    return Adapter.TotalNumberOfProducts().GetValueOrDefault();
}

Метод DAL s TotalNumberOfProducts возвращает целое число, допускающее значение null, но мы создали ProductsBLL метод класса s, TotalNumberOfProducts чтобы он возвращал стандартное целое число.The DAL s TotalNumberOfProducts method returns a nullable integer; however, we ve created the ProductsBLL class s TotalNumberOfProducts method so that it returns a standard integer. Поэтому необходимо, чтобы ProductsBLL метод класса s TotalNumberOfProducts возвращал часть значения обнуляемого целого числа, возвращенного методом DAL s TotalNumberOfProducts .Therefore, we need to have the ProductsBLL class s TotalNumberOfProducts method return the value portion of the nullable integer returned by the DAL s TotalNumberOfProducts method. Вызов GetValueOrDefault() возвращает значение обнуляемого целого числа, если оно существует; если значение, допускающее значения NULL null , равно, то возвращается целочисленное значение по умолчанию 0.The call to GetValueOrDefault() returns the value of the nullable integer, if it exists; if the nullable integer is null, however, it returns the default integer value, 0.

Шаг 3. возврат точного подмножества записейStep 3: Returning the Precise Subset of Records

Следующей задачей является создание методов DAL и BLL, принимающих переменные начальной строки и максимального числа строк, которые обсуждались ранее, и возвращают соответствующие записи.Our next task is to create methods in the DAL and BLL that accept the Start Row Index and Maximum Rows variables discussed earlier and return the appropriate records. Прежде чем это сделать, давайте взглянем на необходимый скрипт SQL.Before we do that, let s first look at the needed SQL script. Проблема заключается в том, что мы должны иметь возможность эффективно назначать индекс каждой строке во всех результатах, чтобы мы могли возвращать только те записи, начиная с индекса начальной строки (и до максимального количества записей).The challenge facing us is that we must be able to efficiently assign an index to each row in the entire results being paged through so that we can return just those records starting at the Start Row Index (and up to the Maximum Records number of records).

Это не является проблемой, если в таблице базы данных уже есть столбец, который служит индексом строки.This is not a challenge if there is already a column in the database table that serves as a row index. На первый взгляд может показаться, что Products поле Table s ProductID достаточно, так как первый продукт имеет значение ProductID 1, второй — 2 и т. д.At first glance we might think that the Products table s ProductID field would suffice, as the first product has ProductID of 1, the second a 2, and so on. Однако удаление продукта оставляет разрыв в последовательности, отменяя этот подход.However, deleting a product leaves a gap in the sequence, nullifying this approach.

Существует два общих метода, с помощью которых можно эффективно связать индекс строки с данными для постраничного просмотра, тем самым обеспечивая точное подмножество извлекаемых записей:There are two general techniques used to efficiently associate a row index with the data to page through, thereby enabling the precise subset of records to be retrieved:

  • Использование SQL Server 2005 s ROW_NUMBER() Ключевое слово New для SQL Server 2005, ROW_NUMBER() ключевое слово связывает ранжирование с каждой возвращаемой записью на основе определенного порядка.Using SQL Server 2005 s ROW_NUMBER() Keyword new to SQL Server 2005, the ROW_NUMBER() keyword associates a ranking with each returned record based on some ordering. Этот рейтинг можно использовать в качестве индекса строки для каждой строки.This ranking can be used as a row index for each row.

  • **Использование табличной переменной и SET ROWCOUNT ** С помощью SET ROWCOUNT инструкции SQL Server s можно указать, сколько записей будет обрабатывать запрос перед завершением работы. табличные переменные — это локальные переменные T-SQL, которые могут содержать табличные данные, в аналогах с временными таблицами.Using a Table Variable and SET ROWCOUNT SQL Server s SET ROWCOUNT statement can be used to specify how many total records a query should process before terminating; table variables are local T-SQL variables that can hold tabular data, akin to temporary tables. Этот подход работает одинаково хорошо с Microsoft SQL Server 2005 и SQL Server 2000 (в то время как ROW_NUMBER() подход работает только с SQL Server 2005).This approach works equally well with both Microsoft SQL Server 2005 and SQL Server 2000 (whereas the ROW_NUMBER() approach only works with SQL Server 2005).

    Идея состоит в том, чтобы создать табличную переменную со IDENTITY столбцом и столбцами для первичных ключей таблицы, данные на которых разгружаются по страницам.The idea here is to create a table variable that has an IDENTITY column and columns for the primary keys of the table whose data is being paged through. Далее содержимое таблицы, данные из которой догружаются по страницам, копируется в табличную переменную, тем самым связывая индекс последовательной строки (через IDENTITY столбец) для каждой записи в таблице.Next, the contents of the table whose data is being paged through is dumped into the table variable, thereby associating a sequential row index (via the IDENTITY column) for each record in the table. После заполнения табличной переменной SELECT инструкция в табличной переменной, присоединенной к базовой таблице, может быть выполнена для извлечения определенных записей.Once the table variable has been populated, a SELECT statement on the table variable, joined with the underlying table, can be executed to pull out the particular records. SET ROWCOUNTИнструкция используется для разумного ограничения количества записей, которые необходимо выгрузить в табличную переменную.The SET ROWCOUNT statement is used to intelligently limit the number of records that need to be dumped into the table variable.

    Эффективность этого подхода зависит от запрашиваемого номера страницы, так как этому SET ROWCOUNT значению присваивается значение индекса начальной строки плюс максимальное число строк.This approach s efficiency is based on the page number being requested, as the SET ROWCOUNT value is assigned the value of Start Row Index plus the Maximum Rows. При разбиении на страницы с низкой нумерацией, например первые несколько страниц данных, этот подход очень эффективен.When paging through low-numbered pages such as the first few pages of data this approach is very efficient. Однако при извлечении страницы рядом с ней используется производительность по умолчанию, аналогичная разбиению по страницам.However, it exhibits default paging-like performance when retrieving a page near the end.

В этом руководстве реализуется пользовательский разбиение на страницы с помощью ROW_NUMBER() ключевого слова.This tutorial implements custom paging using the ROW_NUMBER() keyword. Дополнительные сведения об использовании табличной переменной и SET ROWCOUNT методики см. в разделе более эффективный метод разбиения по страницам больших результирующих наборов.For more information on using the table variable and SET ROWCOUNT technique, see A More Efficient Method for Paging Through Large Result Sets.

ROW_NUMBER()Ключевое слово связывает ранжирование с каждой записью, возвращенной в определенном порядке, с помощью следующего синтаксиса:The ROW_NUMBER() keyword associated a ranking with each record returned over a particular ordering using the following syntax:

SELECT columnList,
       ROW_NUMBER() OVER(orderByClause)
FROM TableName

ROW_NUMBER() Возвращает числовое значение, указывающее ранг каждой записи в отношении указанного упорядочения.ROW_NUMBER() returns a numerical value that specifies the rank for each record with regards to the indicated ordering. Например, чтобы просмотреть рейтинг каждого продукта, упорядоченный от наиболее дорогих к наименьшему, можно использовать следующий запрос:For example, to see the rank for each product, ordered from the most expensive to the least, we could use the following query:

SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
FROM Products

На рис. 5 показаны результаты этого запроса при выполнении в окне запроса в Visual Studio.Figure 5 shows this query s results when run through the query window in Visual Studio. Обратите внимание, что продукты упорядочиваются по цене вместе с рейтингом цены для каждой строки.Note that the products are ordered by price, along with a price rank for each row.

Рейтинг цены включен для каждой возвращенной записи

Рис. 5. рейтинг цены включен для каждой возвращенной записиFigure 5: The Price Rank is Included for Each Returned Record

Note

ROW_NUMBER() — Это лишь одна из многих новых функций ранжирования, доступных в SQL Server 2005.ROW_NUMBER() is just one of the many new ranking functions available in SQL Server 2005. Дополнительные сведения о ROW_NUMBER() , а также другие ранжирующие функции см. в статье Получение ранжированных результатов с помощью Microsoft SQL Server 2005.For a more thorough discussion of ROW_NUMBER(), along with the other ranking functions, read Returning Ranked Results with Microsoft SQL Server 2005.

При ранжировании результатов по указанному ORDER BY столбцу в OVER предложении ( UnitPrice в приведенном выше примере) SQL Server должны сортировать результаты.When ranking the results by the specified ORDER BY column in the OVER clause (UnitPrice, in the above example), SQL Server must sort the results. Это быстрая операция, если имеется кластеризованный индекс по столбцам, в котором упорядочиваются результаты, или если присутствует индекс, который в противном случае может быть более затратным.This is a quick operation if there is a clustered index over the column(s) the results are being ordered by, or if there is a covering index, but can be more costly otherwise. Чтобы повысить производительность для достаточно больших запросов, рассмотрите возможность добавления некластеризованного индекса для столбца, по которому упорядочиваются результаты.To help improve performance for sufficiently large queries, consider adding a non-clustered index for the column by which the results are ordered by. Более подробные сведения о производительности см. в разделе ранжирующие функции и производительность в SQL Server 2005 .See Ranking Functions and Performance in SQL Server 2005 for a more detailed look at the performance considerations.

Сведения о ранжировании, возвращаемые, ROW_NUMBER() нельзя использовать в WHERE предложении напрямую.The ranking information returned by ROW_NUMBER() cannot directly be used in the WHERE clause. Однако производную таблицу можно использовать для возврата ROW_NUMBER() результата, который затем может появиться в WHERE предложении.However, a derived table can be used to return the ROW_NUMBER() result, which can then appear in the WHERE clause. Например, следующий запрос использует производную таблицу для возврата столбцов ProductName и UnitPrice вместе с ROW_NUMBER() результатом, а затем использует WHERE предложение для возврата только тех продуктов, ранг цен которых составляет от 11 до 20:For example, the following query uses a derived table to return the ProductName and UnitPrice columns, along with the ROW_NUMBER() result, and then uses a WHERE clause to only return those products whose price rank is between 11 and 20:

SELECT PriceRank, ProductName, UnitPrice
FROM
   (SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
    FROM Products
   ) AS ProductsWithRowNumber
WHERE PriceRank BETWEEN 11 AND 20

Более того, чтобы расширить эту концепцию, мы можем использовать этот подход для получения определенной страницы данных по заданному индексу начальной строки и максимальным значениям строк:Extending this concept a bit further, we can utilize this approach to retrieve a specific page of data given the desired Start Row Index and Maximum Rows values:

SELECT PriceRank, ProductName, UnitPrice
FROM
   (SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
    FROM Products
   ) AS ProductsWithRowNumber
WHERE PriceRank > <i>StartRowIndex</i> AND
    PriceRank <= (<i>StartRowIndex</i> + <i>MaximumRows</i>)

Note

Как будет показано далее в этом учебнике, объект, StartRowIndex предоставленный ObjectDataSource, индексируется начиная с нуля, а ROW_NUMBER() значение, возвращаемое SQL Server 2005, индексируется начиная с 1.As we will see later on in this tutorial, the StartRowIndex supplied by the ObjectDataSource is indexed starting at zero, whereas the ROW_NUMBER() value returned by SQL Server 2005 is indexed starting at 1. Таким образом, WHERE предложение возвращает те записи, PriceRank в которых строго больше StartRowIndex и меньше или равно StartRowIndex + MaximumRows .Therefore, the WHERE clause returns those records where PriceRank is strictly greater than StartRowIndex and less than or equal to StartRowIndex + MaximumRows.

Теперь, когда мы рассмотрели, как ROW_NUMBER() можно использовать для получения определенной страницы данных с учетом значений индекса начальной и максимальной строк, нам нужно реализовать эту логику как методы DAL и BLL.Now that we ve discussed how ROW_NUMBER() can be used to retrieve a particular page of data given the Start Row Index and Maximum Rows values, we now need to implement this logic as methods in the DAL and BLL.

При создании этого запроса необходимо выбрать упорядочивание, по которому будут ранжированы результаты. Позвольте s Сортировать продукты по именам в алфавитном порядке.When creating this query we must decide the ordering by which the results will be ranked; let s sort the products by their name in alphabetical order. Это означает, что при реализации пользовательского разбиения по страницам в этом учебнике не будет возможности создать пользовательский отчет с разбивкой на страницы, чем также можно будет отсортировать.This means that with the custom paging implementation in this tutorial we will not be able to create a custom paged report than can also be sorted. Тем не менее, в следующем учебном курсе мы увидим, как можно предоставить такую функциональность.In the next tutorial, though, we'll see how such functionality can be provided.

В предыдущем разделе мы создали метод DAL в качестве специального оператора SQL.In the previous section we created the DAL method as an ad-hoc SQL statement. К сожалению, средство синтаксического анализа T-SQL в Visual Studio, используемое мастером TableAdapter, не похоже на OVER синтаксис, используемый ROW_NUMBER() функцией.Unfortunately, the T-SQL parser in Visual Studio used by the TableAdapter wizard doesn t like the OVER syntax used by the ROW_NUMBER() function. Поэтому необходимо создать этот метод DAL в качестве хранимой процедуры.Therefore, we must create this DAL method as a stored procedure. Выберите обозреватель сервера в меню Вид (или нажмите клавиши CTRL + ALT + S) и разверните NORTHWND.MDF узел.Select the Server Explorer from the View menu (or hit Ctrl+Alt+S) and expand the NORTHWND.MDF node. Чтобы добавить новую хранимую процедуру, щелкните правой кнопкой мыши узел Хранимые процедуры и выберите команду Добавить новую хранимую процедуру (см. рис. 6).To add a new stored procedure, right-click on the Stored Procedures node and choose Add a New Stored Procedure (see Figure 6).

Добавление новой хранимой процедуры для разбиения по страницам продуктов

Рис. 6. Добавление новой хранимой процедуры для разбиения по страницам продуктовFigure 6: Add a New Stored Procedure for Paging Through the Products

Эта хранимая процедура должна принимать два целочисленных входных параметра — @startRowIndex и @maximumRows использовать ROW_NUMBER() функцию, упорядоченную по ProductName полю, возвращая только те строки, которые больше заданного значения @startRowIndex и меньше или равны @startRowIndex + @maximumRow .This stored procedure should accept two integer input parameters - @startRowIndex and @maximumRows and use the ROW_NUMBER() function ordered by the ProductName field, returning only those rows greater than the specified @startRowIndex and less than or equal to @startRowIndex + @maximumRow s. Введите следующий скрипт в новую хранимую процедуру, а затем щелкните значок Сохранить, чтобы добавить хранимую процедуру в базу данных.Enter the following script into the new stored procedure and then click the Save icon to add the stored procedure to the database.

CREATE PROCEDURE dbo.GetProductsPaged
(
    @startRowIndex int,
    @maximumRows int
)
AS
    SELECT     ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
               UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
               CategoryName, SupplierName
FROM
   (
       SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
              UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
              (SELECT CategoryName
               FROM Categories
               WHERE Categories.CategoryID = Products.CategoryID) AS CategoryName,
              (SELECT CompanyName
               FROM Suppliers
               WHERE Suppliers.SupplierID = Products.SupplierID) AS SupplierName,
              ROW_NUMBER() OVER (ORDER BY ProductName) AS RowRank
        FROM Products
    ) AS ProductsWithRowNumbers
WHERE RowRank > @startRowIndex AND RowRank <= (@startRowIndex + @maximumRows)

После создания хранимой процедуры уделите время ее протестировать. Щелкните правой кнопкой мыши GetProductsPaged имя хранимой процедуры в обозреватель сервера и выберите пункт Выполнить.After creating the stored procedure, take a moment to test it out. Right-click on the GetProductsPaged stored procedure name in the Server Explorer and choose the Execute option. В Visual Studio будет предложено ввести входные параметры @startRowIndex и @maximumRow s (см. рис. 7).Visual Studio will then prompt you for the input parameters, @startRowIndex and @maximumRow s (see Figure 7). Попробуйте использовать другие значения и проверьте результаты.Try different values and examine the results.

Введите значение для @startRowIndex @maximumRows параметров и

Рис. 7. Ввод значения для @startRowIndex параметров и @maximumRowsFigure 7: Enter a Value for the @startRowIndex and @maximumRows Parameters

После выбора значений входных параметров в окне вывода отобразятся результаты.After choosing these input parameters values, the Output window will show the results. На рис. 8 показаны результаты при передаче в 10 для @startRowIndex параметров и @maximumRows .Figure 8 shows the results when passing in 10 for both the @startRowIndex and @maximumRows parameters.

Будут возвращены записи, которые будут отображаться на второй странице данных.The Records That Would Appear in the Second Page of Data are Returned

Рис. 8. возвращаются записи, которые будут отображаться на второй странице данных (щелкните, чтобы просмотреть изображение с полным размером)Figure 8: The Records That Would Appear in the Second Page of Data are Returned (Click to view full-size image)

После создания этой хранимой процедуры мы повторно готовы к созданию ProductsTableAdapter метода.With this stored procedure created, we re ready to create the ProductsTableAdapter method. Откройте Northwind.xsd типизированный набор данных, щелкните правой кнопкой мыши в ProductsTableAdapter и выберите пункт Добавить запрос.Open the Northwind.xsd Typed DataSet, right-click in the ProductsTableAdapter, and choose the Add Query option. Вместо создания запроса с помощью специальной инструкции SQL создайте ее с помощью существующей хранимой процедуры.Instead of creating the query using an ad-hoc SQL statement, create it using an existing stored procedure.

Создание метода DAL с помощью существующей хранимой процедуры

Рис. 9. Создание метода DAL с помощью существующей хранимой процедурыFigure 9: Create the DAL Method Using an Existing Stored Procedure

Далее будет предложено выбрать хранимую процедуру для вызова.Next, we are prompted to select the stored procedure to invoke. Выберите GetProductsPaged хранимую процедуру из раскрывающегося списка.Pick the GetProductsPaged stored procedure from the drop-down list.

Выберите хранимую процедуру GetProductsPaged из раскрывающегося списка.

Рис. 10. Выбор хранимой процедуры GetProductsPaged из раскрывающегося спискаFigure 10: Choose the GetProductsPaged Stored Procedure from the Drop-Down List

Затем на следующем экране запрашивается тип данных, возвращаемых хранимой процедурой: табличные данные, одно значение или значение No.The next screen then asks you what kind of data is returned by the stored procedure: tabular data, a single value, or no value. Так как GetProductsPaged хранимая процедура может возвращать несколько записей, укажите, что она возвращает табличные данные.Since the GetProductsPaged stored procedure can return multiple records, indicate that it returns tabular data.

Указывает, что хранимая процедура возвращает табличные данные

Рис. 11. Указание того, что хранимая процедура возвращает табличные данныеFigure 11: Indicate that the Stored Procedure Returns Tabular Data

Наконец, укажите имена методов, которые необходимо создать.Finally, indicate the names of the methods you want to have created. Как и в предыдущих учебных курсах, вы можете создавать методы, используя как заполнение DataTable, так и возвратить таблицу данных.As with our previous tutorials, go ahead and create methods using both the Fill a DataTable and Return a DataTable. Назовите первый метод FillPaged и второй GetProductsPaged .Name the first method FillPaged and the second GetProductsPaged.

Назовите методы Филлпажед и GetProductsPaged

Рис. 12. Назовите методы Филлпажед и GetProductsPagedFigure 12: Name the Methods FillPaged and GetProductsPaged

Помимо создания метода DAL для возврата определенной страницы продуктов, нам также нужно предоставить такую функциональность в BLL.In addition to created a DAL method to return a particular page of products, we also need to provide such functionality in the BLL. Как и метод DAL, метод BLL GetProductsPaged должен принимать два целочисленных значения для указания индекса начальной и максимальной строк, а также должен возвращать только те записи, которые попадают в указанный диапазон.Like the DAL method, the BLL s GetProductsPaged method must accept two integer inputs for specifying the Start Row Index and Maximum Rows, and must return just those records that fall within the specified range. Создайте такой метод BLL в классе ProductsBLL, который просто вызывает метод DAL s GetProductsPaged, вот так:Create such a BLL method in the ProductsBLL class that merely calls down into the DAL s GetProductsPaged method, like so:

[System.ComponentModel.DataObjectMethodAttribute(
    System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsPaged(int startRowIndex, int maximumRows)
{
    return Adapter.GetProductsPaged(startRowIndex, maximumRows);
}

Для входных параметров метода BLL можно использовать любое имя, но, как мы увидим чуть позже, startRowIndex maximumRows при настройке ObjectDataSource для использования этого метода вы сможете использовать и сохранить нас из лишней работы.You can use any name for the BLL method s input parameters, but, as we will see shortly, choosing to use startRowIndex and maximumRows saves us from an extra bit of work when configuring an ObjectDataSource to use this method.

Шаг 4. Настройка элемента управления ObjectDataSource для использования пользовательского разбиения на страницыStep 4: Configuring the ObjectDataSource to Use Custom Paging

С помощью методов BLL и DAL для получения доступа к определенному подмножеству записей мы повторно готовы к созданию элемента управления GridView, который просматривает базовые записи, используя пользовательское разбиение по страницам.With the BLL and DAL methods for accessing a particular subset of records complete, we re ready to create a GridView control that pages through its underlying records using custom paging. Сначала откройте EfficientPaging.aspx страницу в PagingAndSorting папке, добавьте GridView на страницу и настройте ее для использования нового элемента управления ObjectDataSource.Start by opening the EfficientPaging.aspx page in the PagingAndSorting folder, add a GridView to the page, and configure it to use a new ObjectDataSource control. В прошлых учебных курсах мы часто настроили ObjectDataSource на использование ProductsBLL метода Class s GetProducts .In our past tutorials, we often had the ObjectDataSource configured to use the ProductsBLL class s GetProducts method. Но на этот раз мы хотим использовать GetProductsPaged вместо этого метод, так как GetProducts метод возвращает все продукты в базе данных, тогда как GetProductsPaged возвращает только определенное подмножество записей.This time, however, we want to use the GetProductsPaged method instead, since the GetProducts method returns all of the products in the database whereas GetProductsPaged returns just a particular subset of records.

Настройка ObjectDataSource для использования метода GetProductsPaged класса ProductsBLL

Рис. 13. Настройка ObjectDataSource для использования метода GetProductsPaged класса ProductsBLLFigure 13: Configure the ObjectDataSource to Use the ProductsBLL Class s GetProductsPaged Method

Так как мы повторно создаем GridView, доступное только для чтения, задавайте в раскрывающийся список метод на вкладках Вставка, обновление и удаление значение (нет).Since we re creating a read-only GridView, take a moment to set the method drop-down list in the INSERT, UPDATE, and DELETE tabs to (None).

Затем мастер ObjectDataSource запрашивает источники GetProductsPaged значений методов s startRowIndex и maximumRows input Parameters.Next, the ObjectDataSource wizard prompts us for the sources of the GetProductsPaged method s startRowIndex and maximumRows input parameters values. Эти входные параметры фактически задаются в элементе управления GridView автоматически, поэтому просто оставьте исходное значение None и нажмите кнопку Готово.These input parameters will actually be set by the GridView automatically, so simply leave the source set to None and click Finish.

Оставить источникам входных параметров значение None

Рис. 14. Оставьте источники входных параметров как нетFigure 14: Leave the Input Parameter Sources as None

После завершения работы мастера ObjectDataSource элемент GridView будет содержать BoundField или CheckBoxField для каждого поля данных продукта.After completing the ObjectDataSource wizard, the GridView will contain a BoundField or CheckBoxField for each of the product data fields. Вы можете адаптировать внешний вид GridView s по своему усмотрению.Feel free to tailor the GridView s appearance as you see fit. Я решил отобразить только,,, ProductName CategoryName SupplierName QuantityPerUnit и UnitPrice BoundFields.I ve opted to display only the ProductName, CategoryName, SupplierName, QuantityPerUnit, and UnitPrice BoundFields. Кроме того, настройте GridView для поддержки разбиения на страницы, установив флажок Включить разбиение по страницам в его смарт-теге.Also, configure the GridView to support paging by checking the Enable Paging checkbox in its smart tag. После этих изменений декларативная разметка GridView и ObjectDataSource должна выглядеть следующим образом:After these changes, the GridView and ObjectDataSource declarative markup should look similar to the following:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
            SortExpression="SupplierName" />
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
            SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}"
            HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsPaged"
    TypeName="ProductsBLL">
    <SelectParameters>
        <asp:Parameter Name="startRowIndex" Type="Int32" />
        <asp:Parameter Name="maximumRows" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

Однако если вы посещаете страницу в браузере, GridView не сможет найти.If you visit the page through a browser, however, the GridView is no where to be found.

Элемент управления GridView не отображается

Рис. 15. элемент управления GridView не отображаетсяFigure 15: The GridView is Not Displayed

Элемент GridView отсутствует, поскольку ObjectDataSource в настоящий момент использует 0 в качестве значений для обоих GetProductsPaged startRowIndex maximumRows входных параметров и.The GridView is missing because the ObjectDataSource is currently using 0 as the values for both of the GetProductsPaged startRowIndex and maximumRows input parameters. Таким образом, результирующий запрос SQL не возвращает никаких записей, поэтому GridView не отображается.Hence, the resulting SQL query is returning no records and therefore the GridView is not displayed.

Чтобы устранить эту проблему, необходимо настроить ObjectDataSource для использования пользовательского разбиения на страницы.To remedy this, we need to configure the ObjectDataSource to use custom paging. Это можно сделать, выполнив следующие действия.This can be accomplished in the following steps:

  1. Присвойте EnablePaging свойству true ObjectDataSource s значение this, указывающее элементу ObjectDataSource, что он должен передаваться в SelectMethod два дополнительных параметра: один для указания индекса начальной строки ( StartRowIndexParameterName ), а другой — для указания максимального числа строк ( MaximumRowsParameterName ).Set the ObjectDataSource s EnablePaging property to true this indicates to the ObjectDataSource that it must pass to the SelectMethod two additional parameters: one to specify the Start Row Index (StartRowIndexParameterName), and one to specify the Maximum Rows (MaximumRowsParameterName).
  2. Задайте для элементов ObjectDataSource StartRowIndexParameterName и MaximumRowsParameterName Properties соответствующие StartRowIndexParameterName Свойства и MaximumRowsParameterName указывают имена входных параметров, передаваемых в SelectMethod для настраиваемого разбиения по страницам.Set the ObjectDataSource s StartRowIndexParameterName and MaximumRowsParameterName Properties Accordingly the StartRowIndexParameterName and MaximumRowsParameterName properties indicate the names of the input parameters passed into the SelectMethod for custom paging purposes. По умолчанию эти имена параметров являются startIndexRow и maximumRows , поэтому при создании GetProductsPaged метода в BLL я использовал эти значения для входных параметров.By default, these parameter names are startIndexRow and maximumRows, which is why, when creating the GetProductsPaged method in the BLL, I used these values for the input parameters. Если выбрано использование различных имен параметров для метода BLL s GetProductsPaged startIndex , например maxRows , и, например, необходимо соответствующим образом задать элементы ObjectDataSource StartRowIndexParameterName и MaximumRowsParameterName Свойства (например, startIndex для StartRowIndexParameterName и maxRows для MaximumRowsParameterName ).If you chose to use different parameter names for the BLL s GetProductsPaged method such as startIndex and maxRows, for example you would need to set the ObjectDataSource s StartRowIndexParameterName and MaximumRowsParameterName properties accordingly (such as startIndex for StartRowIndexParameterName and maxRows for MaximumRowsParameterName).
  3. Присвойте SelectCountMethod свойству ObjectDataSource s имя метода, возвращающего общее количество записей, передаваемых по страницам ( TotalNumberOfProducts ) , чтобы ProductsBLL метод класса s TotalNumberOfProducts возвращал общее количество записей, на которые помещается страница, с помощью метода DAL, выполняющего SELECT COUNT(*) FROM Products запрос.Set the ObjectDataSource s SelectCountMethod Property to the Name of the Method that Returns the Total Number of Records Being Paged Through (TotalNumberOfProducts) recall that the ProductsBLL class s TotalNumberOfProducts method returns the total number of records being paged through using a DAL method that executes a SELECT COUNT(*) FROM Products query. Эти сведения необходимы ObjectDataSource для правильного отображения интерфейса разбиения по страницам.This information is needed by the ObjectDataSource in order to correctly render the paging interface.
  4. Удалите startRowIndex элементы и maximumRows <asp:Parameter> из декларативной разметки ObjectDataSource s при настройке ObjectDataSource с помощью мастера Visual Studio автоматически добавил два <asp:Parameter> элемента для GetProductsPaged входных параметров метода.Remove the startRowIndex and maximumRows <asp:Parameter> Elements from the ObjectDataSource s Declarative Markup when configuring the ObjectDataSource through the wizard, Visual Studio automatically added two <asp:Parameter> elements for the GetProductsPaged method s input parameters. Если задано значение EnablePaging true , эти параметры будут передаваться автоматически. Если они также появляются в декларативном синтаксисе, то ObjectDataSource попытается передать четыре параметра GetProductsPaged методу и два параметра в TotalNumberOfProducts метод.By setting EnablePaging to true, these parameters will be passed automatically; if they also appear in the declarative syntax, the ObjectDataSource will attempt to pass four parameters to the GetProductsPaged method and two parameters to the TotalNumberOfProducts method. Если вы забыли удалить эти <asp:Parameter> элементы, при посещении страницы в браузере вы получите следующее сообщение об ошибке: ObjectDataSource ' ObjectDataSource1 ' не может найти неуниверсальный метод ' тоталнумберофпродуктс ' с параметрами: StartRowIndex, maximumRows.If you forget to remove these <asp:Parameter> elements, when visiting the page through a browser you'll get an error message like: ObjectDataSource 'ObjectDataSource1' could not find a non-generic method 'TotalNumberOfProducts' that has parameters: startRowIndex, maximumRows.

После внесения этих изменений декларативный синтаксис ObjectDataSource s должен выглядеть следующим образом:After making these changes, the ObjectDataSource s declarative syntax should look like the following:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsPaged" EnablePaging="True"
    SelectCountMethod="TotalNumberOfProducts">
</asp:ObjectDataSource>

Обратите внимание, что EnablePaging Свойства и были SelectCountMethod установлены и <asp:Parameter> элементы были удалены.Note that the EnablePaging and SelectCountMethod properties have been set and the <asp:Parameter> elements have been removed. На рис. 16 показан снимок экрана окно свойств после внесения этих изменений.Figure 16 shows a screen shot of the Properties window after these changes have been made.

Чтобы использовать пользовательское разбиение на страницы, настройте элемент управления ObjectDataSource

Рисунок 16. Настройка элемента управления ObjectDataSource с помощью пользовательского разбиения на страницыFigure 16: To Use Custom Paging, Configure the ObjectDataSource Control

После внесения этих изменений посетите эту страницу в браузере.After making these changes, visit this page through a browser. Вы должны увидеть 10 продуктов в списке, упорядоченный в алфавитном порядке.You should see 10 products listed, ordered alphabetically. Потратьте время на пошаговое выполнение данных по одной странице.Take a moment to step through the data one page at a time. Хотя визуальное отличие от перспективы конечных пользователей между разбиением по страницам по умолчанию и пользовательским разбиением на страницы не существует, пользовательское разбиение по страницам позволяет более эффективно просматривать большие объемы данных по мере получения только тех записей, которые должны отображаться для данной страницы.While there is no visual difference from the end user s perspective between default paging and custom paging, custom paging more efficiently pages through large amounts of data as it only retrieves those records that need to be displayed for a given page.

Данные, упорядоченные по имени продукта, выявляются страницами с помощью пользовательского разбиения на страницы.The Data, Ordered by the Product s Name, is Paged Using Custom Paging

Рис. 17. данные, упорядоченные по названию продукта, выявляются страницами с помощью пользовательского разбиения на страницы (щелкните, чтобы просмотреть изображение с полным размером).Figure 17: The Data, Ordered by the Product s Name, is Paged Using Custom Paging (Click to view full-size image)

Note

При использовании пользовательского разбиения на страницы значение счетчика страниц, возвращаемое ObjectDataSource, SelectCountMethod сохраняется в состоянии представления GridView.With custom paging, the page count value returned by the ObjectDataSource's SelectCountMethod is stored in the GridView's view state. Другие переменные GridView.,, PageIndex EditIndex SelectedIndex , DataKeys коллекция и т. д. хранятся в состоянии элемента управления, которое сохраняется независимо от значения EnableViewState Свойства GridView.Other GridView variables the PageIndex, EditIndex, SelectedIndex, DataKeys collection, and so on are stored in control state, which is persisted regardless of the value of the GridView's EnableViewState property. Так как PageCount значение сохраняется между обратными передачами с помощью состояния представления, при использовании интерфейса разбиения по страницам, включающего ссылку для перехода на последнюю страницу, необходимо включить состояние представления GridView.Since the PageCount value is persisted across postbacks using view state, when using a paging interface that includes a link to take you to the last page, it is imperative that the GridView's view state be enabled. (Если интерфейс разбиения по страницам не содержит прямую ссылку на последнюю страницу, то можно отключить состояние просмотра.)(If your paging interface does not include a direct link to the last page, then you may disable view state.)

Щелчок последней ссылки на страницу вызывает обратную передачу и указывает GridView обновить PageIndex свойство.Clicking the last page link causes a postback and instructs the GridView to update its PageIndex property. Если щелкнуть ссылку на последнюю страницу, GridView присваивает PageIndex свойству значение, которое меньше его PageCount Свойства.If the last page link is clicked, the GridView assigns its PageIndex property to a value one less than its PageCount property. При отключенном состоянии представления PageCount значение теряется во всех обратных передачах, а PageIndex вместо этого назначается максимальное целое значение.With view state disabled, the PageCount value is lost across postbacks and the PageIndex is assigned the maximum integer value instead. Затем GridView пытается определить начальный индекс строки путем умножения PageSize PageCount свойств и.Next, the GridView attempts to determine the starting row index by multiplying the PageSize and PageCount properties. Это приводит к возникновению, OverflowException так как размер продукта превышает максимально допустимый.This results in an OverflowException since the product exceeds the maximum allowed integer size.

Реализация пользовательского разбиения по страницам и сортировкиImplement Custom Paging and Sorting

Наша текущая реализация пользовательского разбиения по страницам требует, чтобы порядок, в котором данные выдаются с разбивкой на страницы, был задан статически при создании GetProductsPaged хранимой процедуры.Our current custom paging implementation requires that the order by which the data is paged through be specified statically when creating the GetProductsPaged stored procedure. Однако вы могли заметить, что смарт-тег GridView s содержит флажок Включить сортировку в дополнение к параметру Включить разбиение на страницы.However, you may have noted that the GridView s smart tag contains an Enable Sorting checkbox in addition to the Enable Paging option. К сожалению, Добавление поддержки сортировки в GridView с текущей реализацией пользовательского разбиения по страницам сортирует только записи на просматриваемой на данный момент странице данных.Unfortunately, adding sorting support to the GridView with our current custom paging implementation will only sort the records on the currently viewed page of data. Например, если вы настраиваете GridView для поддержки разбиения на страницы, а затем, когда просматриваете первую страницу данных, сортировать по названию продукта в убывающем порядке, порядок продуктов на странице 1 будет реверсирован.For example, if you configure the GridView to also support paging and then, when viewing the first page of data, sort by product name in descending order, it will reverse the order of the products on page 1. Как показано на рис. 18, в качестве первого продукта при сортировке в противоположном алфавитном порядке, например Карнарвон, не учитываются 71 других продуктов, которые выводятся после Карнарвон Tiger по алфавиту. в сортировке учитываются только записи на первой странице.As Figure 18 shows, such shows Carnarvon Tigers as the first product when sorting in reverse alphabetical order, which ignores the 71 other products that come after Carnarvon Tigers, alphabetically; only those records on the first page are considered in the sorting.

Сортируются только данные, отображаемые на текущей страницеOnly the Data Shown on the Current Page is Sorted

Рис. 18. Сортировка только данных, отображаемых на текущей странице (щелкните, чтобы просмотреть изображение с полным размером)Figure 18: Only the Data Shown on the Current Page is Sorted (Click to view full-size image)

Сортировка применяется только к текущей странице данных, так как сортировка происходит после извлечения данных из метода BLL s GetProductsPaged и этот метод возвращает только те записи, которые относятся к конкретной странице.The sorting only applies to the current page of data because the sorting is occurring after the data has been retrieved from the BLL s GetProductsPaged method, and this method only returns those records for the specific page. Чтобы правильно реализовать сортировку, необходимо передать выражение сортировки GetProductsPaged методу, чтобы данные могли быть соответствующим образом упорядочены перед возвратом конкретной страницы данных.To implement sorting correctly, we need to pass the sort expression to the GetProductsPaged method so that the data can be ranked appropriately before returning the specific page of data. Мы посмотрим, как это сделать в следующем руководстве.We'll see how to accomplish this in our next tutorial.

Реализация пользовательского разбиения по страницам и удалениеImplementing Custom Paging and Deleting

При включении функции удаления в элементе управления GridView, данные которого выводятся с помощью настраиваемых методов разбиения по страницам, вы обнаружите, что при удалении последней записи с последней страницы GridView исчезает, а не будет уменьшаться в GridView PageIndex .If you enabling deleting functionality in a GridView whose data is paged using custom paging techniques you will find that when deleting the last record from the last page, the GridView disappears rather than appropriately decrementing the GridView s PageIndex. Чтобы воспроизвести эту ошибку, включите удаление для учебника только что созданного.To reproduce this bug, enable deleting for the tutorial just we just created. Перейдите на последнюю страницу (стр. 9), где вы должны увидеть один продукт, так как мы подкачкой продукты 81, 10 продуктов за раз.Go to the last page (page 9), where you should see a single product since we are paging through 81 products, 10 products at a time. Удалите этот продукт.Delete this product.

После удаления последнего продукта GridView будет автоматически переходить к восьмой странице, и такие функции будут работать с разбиением по страницам по умолчанию.Upon deleting the last product, the GridView should automatically go to the eighth page, and such functionality is exhibited with default paging. Однако при использовании пользовательского разбиения по страницам после удаления последнего продукта на последней странице GridView просто исчезает с экрана.With custom paging, however, after deleting that last product on the last page, the GridView simply disappears from the screen altogether. Точная Причина, по которой это происходит, немного выходит за рамки данного учебника. см. статью Удаление последней записи на последней странице из GridView с настраиваемым разбиением на страницы для сведений низкого уровня, как в источник этой проблемы.The precise reason why this happens is a bit beyond the scope of this tutorial; see Deleting the Last Record on the Last Page from a GridView with Custom Paging for the low-level details as to the source of this problem. В сводке это происходит из-за следующей последовательности действий, выполняемых элементом GridView при нажатии кнопки "Удалить":In summary it s due to the following sequence of steps that are performed by the GridView when the Delete button is clicked:

  1. Удаление записиDelete the record
  2. Получение соответствующих записей, отображаемых для указанных PageIndex и PageSizeGet the appropriate records to display for the specified PageIndex and PageSize
  3. Убедитесь, что параметр не PageIndex превышает количество страниц данных в источнике данных; если это так, автоматически уменьшите свойство GridView s PageIndexCheck to ensure that the PageIndex does not exceed the number of pages of data in the data source; if it does, automatically decrement the GridView s PageIndex property
  4. Свяжите соответствующую страницу данных с элементом GridView, используя записи, полученные на шаге 2.Bind the appropriate page of data to the GridView using the records obtained in Step 2

Проблема обусловлена тем, что на шаге 2, который PageIndex использовался при извлечении отображаемых записей, по-прежнему является PageIndex последней страницей, единственная запись которой была только что удалена.The problem stems from the fact that in Step 2 the PageIndex used when grabbing the records to display is still the PageIndex of the last page whose sole record was just deleted. Поэтому на шаге 2 никакие записи не возвращаются, так как последняя страница данных больше не содержит никаких записей.Therefore, in Step 2, no records are returned since that last page of data no longer contains any records. Затем в шаге 3 в элементе управления GridView понимается, что его PageIndex свойство больше, чем общее число страниц в источнике данных (так как мы удалили последнюю запись на последней странице), и, таким образом, уменьшаем ее PageIndex свойство.Then, in Step 3, the GridView realizes that its PageIndex property is greater than the total number of pages in the data source (since we ve deleted the last record in the last page) and therefore decrements its PageIndex property. На шаге 4 GridView пытается привязать себя к данным, полученным на шаге 2. Однако на шаге 2 не было возвращено ни одной записи, поэтому в результате возвращается пустой элемент GridView.In Step 4 the GridView attempts to bind itself to the data retrieved in Step 2; however, in Step 2 no records were returned, therefore resulting in an empty GridView. При использовании разбиения по страницам по умолчанию эта проблема не имеет значения, так как на шаге 2 все записи извлекаются из источника данных.With default paging, this problem doesn t surface because in Step 2 all records are retrieved from the data source.

Чтобы устранить эту проблему, у нас есть два варианта.To fix this we have two options. Первый заключается в создании обработчика событий для RowDeleted обработчика событий GridView s, который определяет, сколько записей отображалось на странице, которая была только что удалена.The first is to create an event handler for the GridView s RowDeleted event handler that determines how many records were displayed in the page that was just deleted. Если была только одна запись, то только что удаленная запись должна была быть последней, и нам нужно уменьшить GridView s PageIndex .If there was only one record, then the record just deleted must have been the last one and we need to decrement the GridView s PageIndex. Конечно, мы хотим обновить только в случае, PageIndex Если операция удаления фактически была успешной, что можно определить, убедившись, что e.Exception свойство имеет значение null .Of course, we only want to update the PageIndex if the delete operation was actually successful, which can be determined by ensuring that the e.Exception property is null.

Этот подход работает потому, что он обновляет PageIndex после шага 1, но до шага 2.This approach works because it updates the PageIndex after Step 1 but before Step 2. Таким образом, на шаге 2 возвращается соответствующий набор записей.Therefore, in Step 2, the appropriate set of records is returned. Для этого используйте код, подобный приведенному ниже.To accomplish this, use code like the following:

protected void GridView1_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
    // If we just deleted the last row in the GridView, decrement the PageIndex
    if (e.Exception == null && GridView1.Rows.Count == 1)
        // we just deleted the last row
        GridView1.PageIndex = Math.Max(0, GridView1.PageIndex - 1);
}

Альтернативным решением является создание обработчика событий для события ObjectDataSource s RowDeleted и присвоение AffectedRows свойству значения 1.An alternative workaround is to create an event handler for the ObjectDataSource s RowDeleted event and to set the AffectedRows property to a value of 1. После удаления записи в шаге 1 (но перед повторной извлечением данных на шаге 2) GridView обновляет PageIndex свойство, если одна или несколько строк были затронуты операцией.After deleting the record in Step 1 (but before re-retrieving the data in Step 2), the GridView updates its PageIndex property if one or more rows were affected by the operation. Однако AffectedRows свойство не задается элементом ObjectDataSource и поэтому этот шаг опускается.However, the AffectedRows property is not set by the ObjectDataSource and therefore this step is omitted. Один из способов выполнения этого шага — Ручное задание свойства в AffectedRows случае успешного завершения операции удаления.One way to have this step executed is to manually set the AffectedRows property if the delete operation completes successfully. Это можно сделать с помощью кода, подобного приведенному ниже.This can be accomplished using code like the following:

protected void ObjectDataSource1_Deleted(
    object sender, ObjectDataSourceStatusEventArgs e)
{
    // If we get back a Boolean value from the DeleteProduct method and it's true,
    // then we successfully deleted the product. Set AffectedRows to 1
    if (e.ReturnValue is bool && ((bool)e.ReturnValue) == true)
        e.AffectedRows = 1;
}

Код для обоих обработчиков событий можно найти в классе кода программной части EfficientPaging.aspx примера.The code for both of these events handlers can be found in code-behind class of the EfficientPaging.aspx example.

Сравнение производительности по умолчанию и пользовательского разбиения на страницыComparing the Performance of Default and Custom Paging

Поскольку пользовательское разбиение по страницам извлекает только необходимые записи, в то время как разбиение по страницам по умолчанию возвращает все записи для каждой просматриваемой страницы, то ясно, что настраиваемое разбиение по страницам более эффективно, чем разбиениеSince custom paging only retrieves the needed records, whereas default paging returns all of the records for each page being viewed, it s clear that custom paging is more efficient than default paging. Но насколько эффективнее пользовательское разбиение на страницы?But just how much more efficient is custom paging? Какой выигрыш в производительности можно увидеть, перемещаясь с разбиения по страницам по умолчанию на пользовательское разбиение?What sort of performance gains can be seen by moving from default paging to custom paging?

К сожалению, нет ни одного размера, удовлетворяющего всем ответам.Unfortunately, there s no one size fits all answer here. Выигрыш в производительности зависит от ряда факторов, наиболее заметных двух из которых является число страниц, на которых выполняется разгрузка, и нагрузку на сервер базы данных и каналы связи между веб-сервером и сервером базы данных.The performance gain depends on a number of factors, the most prominent two being the number of records being paged through and the load placed on the database server and communication channels between the web server and database server. Для небольших таблиц, имеющих всего несколько десятков записей, разница в производительности может быть незначительной.For small tables with just a few dozen records, the performance difference may be negligible. Однако в больших таблицах с тысячами сотен тысяч строк разница в производительности имеет значение акутом.For large tables, with thousands to hundreds of thousands of rows, though, the performance difference is acute.

В статье «мой пользовательское подкачка» в ASP.NET 2,0 с SQL Server 2005содержится несколько тестов производительности, которые я выполнял, чтобы демонстрировать различия в производительности между этими двумя методами разбиения по страницам в таблице базы данных с 50 000 записей.An article of mine, Custom Paging in ASP.NET 2.0 with SQL Server 2005, contains some performance tests I ran to exhibit the differences in performance between these two paging techniques when paging through a database table with 50,000 records. В этих тестах я рассматривал время выполнения запроса на уровне SQL Server (с помощью SQL Profiler) и на странице ASP.NET с помощью функций трассировки ASP.NET.In these tests I examined both the time to execute the query at the SQL Server level (using SQL Profiler) and at the ASP.NET page using ASP.NET s tracing features. Помните, что эти тесты были запущены в моем окне разработки с одним активным пользователем, и поэтому являются неинженерными и не воспроизводят типичные шаблоны нагрузки веб-сайтов.Keep in mind that these tests were run on my development box with a single active user, and therefore are unscientific and do not mimic typical website load patterns. Независимо от этого результаты показывают относительные различия времени выполнения по умолчанию и пользовательского разбиения на страницы при работе с достаточно большим объемом данных.Regardless, the results illustrate the relative differences in execution time for default and custom paging when working with sufficiently large amounts of data.

СР. Длительность (в секундах)Avg. Duration (sec) ReadsReads
Профайлер SQL по умолчанию для разбиения на страницыDefault Paging SQL Profiler 1,4111.411 383383
Пользовательское разбиение по страницам SQL ProfilerCustom Paging SQL Profiler 0,0020.002 2929
Трассировка ASP.NET по умолчанию для подкачкиDefault Paging ASP.NET Trace 2,3792.379 Н/ДN/A
ASP.NET трассировка пользовательской подкачкиCustom Paging ASP.NET Trace 0,0290.029 Н/ДN/A

Как видите, получение определенной страницы данных требует 354 меньшего числа операций чтения в среднем и завершено в доли времени.As you can see, retrieving a particular page of data required 354 less reads on average and completed in a fraction of the time. На странице ASP.NET пользователь может подготовить страницу к просмотру в течение 1/100-го времени, которое было затрачено при использовании разбиения по страницам по умолчанию.At the ASP.NET page, custom the page was able to render in close to 1/100th of the time it took when using default paging. Дополнительные сведения об этих результатах вместе с кодом и базе данных, которую можно загрузить для воспроизведения тестов в собственной среде, см. в моей статье .See my article for more information on these results along with code and a database you can download to reproduce these tests in your own environment.

СводкаSummary

Разбиение по страницам по умолчанию — очень просто для реализации просто установите флажок Включить разбиение по страницам в смарт-теге Web Control s, но такая простота достигается за счет производительности.Default paging is a cinch to implement just check the Enable Paging checkbox in the data Web control s smart tag but such simplicity comes at the cost of performance. При использовании разбиения по страницам по умолчанию, когда пользователь запрашивает любую страницу данных, возвращаются все записи, несмотря на то, что может отображаться лишь небольшая часть этих записей.With default paging, when a user requests any page of data all records are returned, even though only a tiny fraction of them may be shown. Для борьбы с этими издержками производительности ObjectDataSource предлагает альтернативный вариант подкачки.To combat this performance overhead, the ObjectDataSource offers an alternative paging option custom paging.

В то время как пользовательское разбиение по страницам улучшает производительность по умолчанию, извлекая только те записи, которые должны быть отображены, они больше участвовали в реализации пользовательского разбиения на страницы.While custom paging improves upon default paging s performance issues by retrieving only those records that need to be displayed, it s more involved to implement custom paging. Во-первых, необходимо записать запрос, который правильно (и эффективно) обращается к конкретному подмножеству запрошенных записей.First, a query must be written that correctly (and efficiently) accesses the specific subset of records requested. Это можно сделать несколькими способами. Мы проверили в этом учебнике использование новой функции SQL Server 2005 s ROW_NUMBER() для ранжирования результатов, а затем возвратить только те результаты, ранжирование которых попадает в указанный диапазон.This can be accomplished in a number of ways; the one we examined in this tutorial is to use SQL Server 2005 s new ROW_NUMBER() function to rank results, and then to return just those results whose ranking falls within a specified range. Кроме того, необходимо добавить средства для определения общего количества записей, на которые размещается страница.Furthermore, we need to add a means to determine the total number of records being paged through. После создания этих методов DAL и BLL также необходимо настроить ObjectDataSource таким образом, чтобы он мог определить, сколько всего записей размещается по страницам, и может правильно передать значения индекса начальной и максимальной строк в BLL.After creating these DAL and BLL methods, we also need to configure the ObjectDataSource so that it can determine how many total records are being paged through and can correctly pass the Start Row Index and Maximum Rows values to the BLL.

Хотя реализация пользовательского разбиения по страницам требует выполнения ряда действий и почти не так проста, как разбиение по страницам по умолчанию, настраиваемое разбиение по страницам является обязательным при разбиении на страницы достаточно большого объема данных.While implementing custom paging does require a number of steps and is not nearly as simple as default paging, custom paging is a necessity when paging through sufficiently large amounts of data. По мере изучения результатов, пользовательское разбиение по страницам может проанализировать секунды во время отрисовки страницы ASP.NET и может осветлить нагрузку на сервер базы данных на один или несколько порядков.As the results examined showed, custom paging can shed seconds off of the ASP.NET page render time and can lighten the load on the database server by one ore more orders of magnitude.

Поздравляем с программированием!Happy Programming!

Об автореAbout the Author

Скотт Митчелл, автор семи книг по ASP/ASP. NET и основатель 4GuysFromRolla.com, работал с веб-технологиями Майкрософт с 1998.Scott Mitchell, author of seven ASP/ASP.NET books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies since 1998. Скотт работает как независимый консультант, преподаватель и модуль записи.Scott works as an independent consultant, trainer, and writer. Его последняя книга — Sams обучать себя ASP.NET 2,0 за 24 часа.His latest book is Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Он доступен по адресу mitchell@4GuysFromRolla.com .He can be reached at mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET .or via his blog, which can be found at http://ScottOnWriting.NET.