Эффективное разбиение на страницы больших объемов данных (C#)

Скотт Митчелл

Загрузить PDF-файл

Параметр разбиения по страницам по умолчанию для элемента управления представлением данных не подходит при работе с большими объемами данных, так как его базовый элемент управления источником данных извлекает все записи, даже если отображается только подмножество данных. В таких случаях необходимо обратиться к пользовательскому разбиению по страницам.

Введение

Как мы говорили в предыдущем руководстве, разбиение по страницам можно реализовать одним из двух способов:

  • Разбиение по умолчанию можно реализовать, просто проверив параметр Включить разбиение по страницам в смарт-теге элемента управления данными. однако при просмотре страницы данных ObjectDataSource извлекает все записи, даже если на странице отображается только их подмножество.
  • Настраиваемое разбиение на страницы повышает производительность разбиения по умолчанию, извлекая из базы данных только те записи, которые должны отображаться для определенной страницы данных, запрошенных пользователем; однако для реализации пользовательского разбиения по страницам требуется немного больше усилий, чем подкачки по умолчанию

Из-за простоты реализации просто проверка флажок, и все готово! разбиение по умолчанию является привлекательным вариантом. Его наивный подход к извлечению всех записей, однако, делает его неправдоподобным выбором при разбиении по страницам через достаточно большие объемы данных или для сайтов с большим количеством одновременных пользователей. В таких случаях мы должны обратиться к пользовательскому разбиению по страницам, чтобы обеспечить адаптивную систему.

Задача пользовательского разбиения на страницы заключается в том, чтобы написать запрос, возвращающий точный набор записей, необходимых для определенной страницы данных. К счастью, Microsoft SQL Server 2005 предоставляет новый ключевое слово для ранжирования результатов, что позволяет нам создавать запросы, которые могут эффективно извлекать соответствующее подмножество записей. В этом руководстве мы посмотрим, как использовать этот новый SQL Server 2005 ключевое слово для реализации пользовательского разбиения по страницам в элементе управления GridView. Хотя пользовательский интерфейс для пользовательского разбиения по страницам идентичен интерфейсу для разбиения по страницам по умолчанию, шаг от одной страницы к другой с помощью пользовательского разбиения по страницам может быть на несколько порядков быстрее, чем по умолчанию.

Примечание

Точный прирост производительности, демонстрируемый пользовательским разбиением по страницам, зависит от общего количества записей, которые выгружаются на страницы, и от нагрузки, выполняемой на сервере базы данных. В конце этого руководства мы рассмотрим некоторые приблизительные метрики, демонстрирующие преимущества в производительности, получаемые с помощью пользовательского разбиения по страницам.

Шаг 1. Основные сведения о пользовательском процессе разбиения по страницам

При разбиении данных на страницы точные записи, отображаемые на странице, зависят от страницы запрашиваемых данных и количества записей, отображаемых на странице. Например, предположим, что мы хотим просмотреть 81 продукт, отображая 10 продуктов на странице. При просмотре первой страницы нам нужны продукты от 1 до 10; при просмотре второй страницы нас интересуют продукты с 11 по 20 и т. д.

Существуют три переменные, которые определяют, какие записи необходимо извлечь и как должен отображаться интерфейс подкачки.

  • Запуск row Index — индекс первой строки на странице отображаемых данных; Этот индекс можно вычислить путем умножения индекса страницы на отображаемые записи для каждой страницы и добавления одной из них. Например, при разбиении по страницам записей 10 за раз для первой страницы (индекс страницы которой равен 0) индекс начальной строки равен 0 * 10 + 1 или 1; для второй страницы (индекс страницы которой равен 1) индекс начальной строки равен 1 * 10 + 1 или 11.
  • Максимальное число строк — максимальное количество записей, отображаемых на странице. Эта переменная называется максимальным числом строк, так как для последней страницы может быть возвращено меньше записей, чем размер страницы. Например, при разбиении по страницам в 81 продукте 10 записей на страницу девятая и последняя страница будет содержать только одну запись. Однако ни на странице не будет отображаться больше записей, чем значение Максимальное число строк.
  • Всего записей Подсчитывает общее количество записей, которые выстраиваются на страницы. Хотя эта переменная не требуется для определения записей, извлекаемой для данной страницы, она определяет интерфейс разбиения по страницам. Например, если выстраивается 81 продукт, интерфейс разбиения на страницы будет отображать девять номеров страниц в пользовательском интерфейсе разбиения на страницы.

При разбиении на страницы по умолчанию индекс начальной строки вычисляется как произведение индекса страницы и размера страницы плюс один, тогда как максимальное число строк — это просто размер страницы. Так как разбиение на страницы по умолчанию извлекает все записи из базы данных при отрисовке любой страницы данных, индекс для каждой строки известен, что делает переход на начальную строку индекса строк тривиальной задачей. Кроме того, общее число записей легко доступно, так как это просто количество записей в datatable (или любой объект, используемый для хранения результатов базы данных).

С учетом переменных индекса начальной строки и максимального числа строк пользовательская реализация разбиения по страницам должна возвращать только точное подмножество записей, начиная с индекса начальной строки и до максимального числа строк после этого. Настраиваемое разбиение по страницам обеспечивает две проблемы:

  • Мы должны иметь возможность эффективно связать индекс строки с каждой строкой во всем перестраивание данных, чтобы можно было начать возвращать записи из указанного индекса начальной строки.
  • Необходимо указать общее количество записей, которые выстраиваются на страницы

На следующих двух шагах мы рассмотрим скрипт SQL, необходимый для реагирования на эти две проблемы. В дополнение к скрипту SQL нам также потребуется реализовать методы в DAL и BLL.

Шаг 2. Возврат общего числа записей, которые нужно перестраивание

Прежде чем мы рассмотрим, как получить точное подмножество записей для отображаемой страницы, давайте сначала рассмотрим, как вернуть общее количество записей, которые выстраиваются на страницы. Эти сведения необходимы для правильной настройки пользовательского интерфейса подкачки. Общее количество записей, возвращаемых определенным SQL-запросом, можно получить с помощью агрегатнойCOUNT функции. Например, чтобы определить общее количество записей в Products таблице, можно использовать следующий запрос:

SELECT COUNT(*)
FROM Products

Добавим метод в DAL, который возвращает эти сведения. В частности, мы создадим метод DAL с именем TotalNumberOfProducts() , который выполняет инструкцию, показанную SELECT выше.

Начните с открытия Northwind.xsd файла Typed DataSet в папке App_Code/DAL . Затем щелкните правой ProductsTableAdapter кнопкой мыши в Designer и выберите Добавить запрос. Как мы видели в предыдущих руководствах, это позволит нам добавить новый метод в DAL, который при вызове будет выполнять определенную инструкцию SQL или хранимую процедуру. Как и в случае с методами TableAdapter в предыдущих руководствах, для этого рекомендуется использовать нерегламентированные инструкции SQL.

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

Рис. 1. Использование нерегламентированной инструкции SQL

На следующем экране можно указать тип создаваемого запроса. Так как этот запрос возвращает одно скалярное значение, общее количество записей в Products таблице выбирает SELECT , который возвращает значение singe.

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

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

После указания типа используемого запроса необходимо указать его.

Использование запроса SELECT COUNT(*) FROM Products

Рис. 3. Использование запроса SELECT COUNT(*) FROM Products

Наконец, укажите имя метода . Как упоминалось выше, давайте будем использовать TotalNumberOfProducts.

Назовите метод DAL TotalNumberOfProducts

Рис. 4. Имя метода DAL TotalNumberOfProducts

После нажатия кнопки Готово мастер добавит TotalNumberOfProducts метод в DAL. Скалярные возвращающие методы в DAL возвращают типы, допускающие значение NULL, если результатом sql-запроса является NULL. Однако наш COUNT запрос всегда будет возвращать значение, отличноеNULL от значения; независимо от того, метод DAL возвращает целое число, допускающее значение NULL.

В дополнение к методу DAL нам также нужен метод в BLL. ProductsBLL Откройте файл класса и добавьте TotalNumberOfProducts метод, который просто вызывает метод DAL:TotalNumberOfProducts

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

Метод DAL TotalNumberOfProducts s возвращает целое число, допускающее значение NULL. Однако мы создали ProductsBLL метод класса s TotalNumberOfProducts , чтобы он возвращал стандартное целое число. Поэтому нам нужно, чтобы ProductsBLL метод класса TotalNumberOfProducts возвращал часть значения целого числа, допускающего значение NULL, возвращаемого методом DAL s TotalNumberOfProducts . Вызов возвращает GetValueOrDefault() значение целого числа, допускающего значение NULL, если оно существует. Однако если целое число, допускающее значение NULL, равно null, возвращает целочисленное значение по умолчанию 0.

Шаг 3. Возвращение точного подмножества записей

Следующая задача — создать методы в DAL и BLL, которые принимают переменные индекса начальной строки и максимального числа строк, описанные ранее, и возвращают соответствующие записи. Прежде чем это сделать, давайте рассмотрим необходимый скрипт SQL. Проблема, стоящая перед нами, заключается в том, что мы должны быть в состоянии эффективно назначить индекс каждой строке во всем выстраивном результатах, чтобы мы могли возвращать только те записи, которые начинаются с индекса начальной строки (и до максимального числа записей).

Это не является проблемой, если в таблице базы данных уже есть столбец, который служит индексом строки. На первый взгляд может показаться, что Products поля таблицы ProductID будет достаточно, так как первый продукт имеет ProductID значение 1, второй — 2 и т. д. Однако удаление продукта оставляет пробел в последовательности, что обнуляет этот подход.

Существует два общих метода, используемых для эффективного связывания индекса строки с данными для перестраиваться, что позволяет получить точное подмножество записей:

  • Используя SQL Server 2005 сROW_NUMBER(). Ключевое слово new to SQL Server 2005, ROW_NUMBER() ключевое слово связывает ранжирование с каждой возвращаемой записью на основе определенного порядка. Это ранжирование можно использовать в качестве индекса строки для каждой строки.

  • Использование табличной переменной и SET ROWCOUNT SQL Server инструкцииSET ROWCOUNT s можно использовать для указания общего количества записей, которые должны быть обработаны запросом перед завершением; переменные таблицы — это локальные переменные T-SQL, которые могут содержать табличные данные, сродни временным таблицам. Этот подход одинаково хорошо работает как с Microsoft SQL Server 2005, так и с SQL Server 2000 (в то время как ROW_NUMBER() он работает только с SQL Server 2005).

    Идея заключается в создании табличной переменной, которая содержит IDENTITY столбец и столбцы для первичных ключей таблицы, данные которой передаются на страницы. Затем содержимое таблицы, данные которой выстраиваются на страницы, помещается в табличную переменную, тем самым связывая последовательный индекс строк (через IDENTITY столбец) для каждой записи в таблице. После заполнения табличной переменной SELECT можно выполнить оператор для табличной переменной, объединенный с базовой таблицей, чтобы извлечь определенные записи. Оператор SET ROWCOUNT используется для интеллектуального ограничения количества записей, которые необходимо отправить в табличную переменную.

    Эффективность этого подхода зависит от запрашиваемого номера страницы, так как ему SET ROWCOUNT присваивается значение индекса начальной строки и максимальное число строк. Этот подход очень эффективен при разбиении по страницам с низкими номерами, например на первых нескольких страницах данных. Однако при извлечении страницы ближе к концу он демонстрирует производительность по умолчанию, похожую на разбиение на страницы.

В этом руководстве реализуется настраиваемое разбиение по страницам ROW_NUMBER() с помощью ключевое слово. Дополнительные сведения об использовании табличной переменной и SET ROWCOUNT метода см. в разделе Эффективное разбиение по страницам с помощью больших объемов данных.

Ключевое слово ROW_NUMBER() связывает ранжирование с каждой записью, возвращаемой по определенному упорядочению, с помощью следующего синтаксиса:

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

ROW_NUMBER() возвращает числовое значение, указывающее ранг каждой записи в отношении указанного порядка. Например, чтобы просмотреть ранг для каждого продукта, заказанного от самого дорогого до наименьшего, можно использовать следующий запрос:

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

На рисунке 5 показаны результаты этого запроса при выполнении через окно запроса в Visual Studio. Обратите внимание, что продукты упорядочены по цене, а также по ценовому рейтингу для каждой строки.

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

Рис. 5. Ценовой ранг включен для каждой возвращенной записи

Примечание

ROW_NUMBER()является лишь одной из многих новых функций ранжирования, доступных в SQL Server 2005 году. Более подробное обсуждение ROW_NUMBER(), а также других функций ранжирования см. в статье Возвращение ранжированных результатов с помощью Microsoft SQL Server 2005.

При ранжировании результатов по указанному ORDER BY столбцу в предложении OVER (UnitPriceв приведенном выше примере) SQL Server должны отсортировать результаты. Это быстрая операция при наличии кластеризованного индекса по столбцам, по которым упорядочены результаты, или если имеется покрывающий индекс, но в противном случае это может быть более дорогостоящим. Чтобы повысить производительность для достаточно больших запросов, рассмотрите возможность добавления некластинного индекса для столбца, по которому упорядочены результаты. Дополнительные сведения о производительности см. в статье Ранжирование функций и производительности в SQL Server 2005 г.

Сведения о ранжировании, возвращаемые , ROW_NUMBER() нельзя напрямую использовать в предложении WHERE . Однако для возврата ROW_NUMBER() результата можно использовать производную таблицу, которая затем может появиться в предложении WHERE . Например, следующий запрос использует производную таблицу для возврата столбцов ProductName и UnitPrice вместе с ROW_NUMBER() результатом WHERE , а затем использует предложение для возврата только тех продуктов, ценовая категория которых составляет от 11 до 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

Расширяя эту концепцию, мы можем использовать этот подход для получения определенной страницы данных с учетом требуемых значений начального индекса строки и максимального числа строк:

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>)

Примечание

Как мы увидим далее в этом руководстве, StartRowIndex объект ObjectDataSource индексируется с нуля, тогда как ROW_NUMBER() значение, возвращаемое SQL Server 2005, индексируется начиная с 1. Таким образом, предложение возвращает те записи, где строго больше StartRowIndex и меньше или равно + StartRowIndexMaximumRows .PriceRankWHERE

Теперь, когда мы рассмотрели, как ROW_NUMBER() можно использовать для извлечения определенной страницы данных с учетом значений Индекса начальной строки и Максимального числа строк, нам нужно реализовать эту логику в качестве методов в DAL и BLL.

При создании этого запроса необходимо определить порядок, по которому будут ранжированы результаты; отсортировать продукты по их имени в алфавитном порядке. Это означает, что при реализации пользовательского разбиения по страницам в этом руководстве мы не сможем создать настраиваемый отчет с разбивкой на страницы, чем можно также отсортировать. Однако в следующем руководстве мы посмотрим, как можно предоставить такие функциональные возможности.

В предыдущем разделе мы создали метод DAL в виде нерегламентированной инструкции SQL. К сожалению, средство синтаксического анализа T-SQL в Visual Studio, используемое мастером TableAdapter, не любит OVER синтаксис, используемый функцией ROW_NUMBER() . Поэтому мы должны создать этот метод DAL как хранимую процедуру. Выберите сервер Обозреватель в меню Вид (или нажмите клавиши CTRL+ALT+S) и разверните NORTHWND.MDF узел. Чтобы добавить новую хранимую процедуру, щелкните правой кнопкой мыши узел Хранимые процедуры и выберите команду Добавить новую хранимую процедуру (см. рис. 6).

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

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

Эта хранимая процедура должна принимать два целочисленных входных параметра — и использовать функцию, упорядоченную по ProductName полю@startRowIndex + @maximumRow, возвращая только те строки, которые больше указанного @startRowIndex значения и меньше или равны s.ROW_NUMBER()@maximumRows@startRowIndex Введите следующий скрипт в новую хранимую процедуру и щелкните значок Сохранить, чтобы добавить хранимую процедуру в базу данных.

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 процедуры в Обозреватель сервера и выберите команду Выполнить. Затем Visual Studio запросит входные параметры @startRowIndex и @maximumRow (см. рис. 7). Попробуйте использовать разные значения и изучить результаты.

Введите значение для <span class=@startRowIndex and @maximumRows Parameters" />

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

После выбора этих значений входных параметров в окне Вывод отобразятся результаты. На рисунке 8 показаны результаты при передаче 10 для @startRowIndex параметров и @maximumRows .

Возвращаются записи, которые будут отображаться на второй странице данных

Рис. 8. Возвращаются записи, которые будут отображаться на второй странице данных (щелкните для просмотра полноразмерного изображения)

Создав эту хранимую процедуру, мы готовы к созданию ProductsTableAdapter метода . Откройте типизированный Northwind.xsd набор данных, щелкните правой ProductsTableAdapterкнопкой мыши элемент и выберите параметр Добавить запрос. Вместо создания запроса с помощью нерегламентированной инструкции SQL создайте его с помощью существующей хранимой процедуры.

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

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

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

Выберите хранимую процедуру GetProductsPaged из списка Drop-Down

Рис. 10. Выбор хранимой процедуры GetProductsPaged из списка Drop-Down

Затем на следующем экране отображается вопрос о типах данных, возвращаемых хранимой процедурой: табличные данные, одно значение или без значения. Так как хранимая GetProductsPaged процедура может возвращать несколько записей, укажите, что она возвращает табличные данные.

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

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

Наконец, укажите имена методов, которые вы хотите создать. Как и в предыдущих руководствах, создайте методы с помощью команд Fill a DataTable и Return a DataTable. Назовите первый метод FillPaged , а второй GetProductsPaged— .

Назовите методы FillPaged и GetProductsPaged

Рис. 12. Имена методов FillPaged и GetProductsPaged

Помимо создания метода DAL для возврата определенной страницы продуктов, необходимо также предоставить такие функции в BLL. Как и метод DAL, метод GetProductsPaged BLL должен принимать два целочисленных входных данных для указания индекса начальной строки и максимального числа строк и возвращать только те записи, которые входят в указанный диапазон. Создайте такой метод BLL в классе ProductsBLL, который просто вызывает метод GetProductsPaged DAL, следующим образом:

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

Вы можете использовать любое имя для входных параметров метода BLL, но, как мы увидим вскоре, выбор и startRowIndexmaximumRows экономит нас от дополнительной работы при настройке ObjectDataSource для использования этого метода.

Шаг 4. Настройка ObjectDataSource для использования пользовательского разбиения по страницам

После завершения работы с методами BLL и DAL для доступа к определенному подмножеству записей мы готовы создать элемент управления GridView, который будет просматривать его базовые записи с помощью пользовательского разбиения по страницам. Начните с открытия EfficientPaging.aspx страницы в папке PagingAndSorting , добавьте gridView на страницу и настройте его для использования нового элемента управления ObjectDataSource. В прошлых руководствах мы часто настроили ObjectDataSource для использования ProductsBLL метода класса .GetProducts Однако на этот раз мы хотим использовать GetProductsPaged метод , так как GetProducts метод возвращает все продукты в базе данных, а GetProductsPaged возвращает только определенное подмножество записей.

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

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

Так как мы повторно создадим доступный только для чтения элемент GridView, укажите для раскрывающегося списка методов на вкладках INSERT, UPDATE и DELETE значение (Нет).

Затем мастер ObjectDataSource запрашивает источники значений GetProductsPaged методов startRowIndex и maximumRows входных параметров. Эти входные параметры будут заданы GridView автоматически, поэтому просто оставьте для источника значение Нет и нажмите кнопку Готово.

Оставьте для параметра Источники входных параметров значение Нет.

Рис. 14. Оставьте для параметра Источники входных параметров значение Нет

После завершения работы мастера ObjectDataSource GridView будет содержать BoundField или CheckBoxField для каждого поля данных продукта. Вы можете настроить внешний вид GridView по своему своему виду. Я решил отображать только ProductNameполя , CategoryName, SupplierNameQuantityPerUnit, и UnitPrice BoundFields. Кроме того, настройте GridView для поддержки разбиения по страницам, установив флажок Включить разбиение по страницам в смарт-теге. После этих изменений декларативная разметка GridView и ObjectDataSource должна выглядеть примерно так:

<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 не будет находиться.

GridView не отображается

Рис. 15. GridView не отображается

Элемент GridView отсутствует, так как ObjectDataSource в настоящее время использует 0 в качестве значений для входных GetProductsPagedstartRowIndex параметров и maximumRows . Таким образом, результирующий SQL-запрос не возвращает записей, поэтому GridView не отображается.

Чтобы устранить эту проблему, необходимо настроить ObjectDataSource для использования пользовательского разбиения по страницам. Это можно сделать, выполнив следующие действия.

  1. Присвойте свойству trueObjectDataSource EnablePaging значение , указывающее, что объекту ObjectDataSource необходимо передать SelectMethod два дополнительных параметра: один для указания индекса начальной строки (StartRowIndexParameterName), а второй — максимальное число строк (MaximumRowsParameterName).
  2. Задайте свойства ObjectDataSource StartRowIndexParameterName и MaximumRowsParameterName СоответственноStartRowIndexParameterName свойства и MaximumRowsParameterName указывают имена входных параметров, передаваемых в SelectMethod для пользовательских целей разбиения по страницам. По умолчанию именами этих параметров являются startIndexRow и maximumRows, поэтому при создании GetProductsPaged метода в BLL я использовал эти значения для входных параметров. Если вы решили использовать разные имена параметров для метода BLL GetProductsPaged , например startIndex и maxRows, необходимо задать свойства ObjectDataSource StartRowIndexParameterName и MaximumRowsParameterName соответственно (например, startIndex для StartRowIndexParameterName и maxRows для MaximumRowsParameterName).
  3. Присвойте свойству ObjectDataSource SelectCountMethod значение Имя метода, который возвращает общее количество записей, до которых выполняется перекачки (TotalNumberOfProducts), напомним, что ProductsBLL метод класса TotalNumberOfProducts возвращает общее количество записей, которые выстраиваются с помощью метода DAL, выполняющего SELECT COUNT(*) FROM Products запрос. Эти сведения требуются ObjectDataSource для правильной отрисовки интерфейса подкачки.
  4. startRowIndex Удалите элементы и maximumRows<asp:Parameter> из декларативной разметки ObjectDataSource. При настройке ObjectDataSource с помощью мастера Visual Studio автоматически добавил два <asp:Parameter> элемента для GetProductsPaged входных параметров метода. Если задать значение EnablePagingtrue, эти параметры будут передаваться автоматически; если они также отображаются в декларативном синтаксисе, ObjectDataSource попытается передать четыре параметра GetProductsPaged в метод и два параметра в TotalNumberOfProducts метод . Если вы забыли удалить эти <asp:Parameter> элементы, при посещении страницы через браузер вы получите сообщение об ошибке, например: ObjectDataSource "ObjectDataSource1" не удалось найти неуниверсальный метод TotalNumberOfProducts с параметрами startRowIndex, maximumRows.

После внесения этих изменений декларативный синтаксис ObjectDataSource должен выглядеть следующим образом:

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

Обратите внимание, что EnablePaging свойства и SelectCountMethod были заданы, <asp:Parameter> а элементы удалены. На рисунке 16 показан снимок экрана с окно свойств после внесения этих изменений.

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

Рис. 16. Настройка элемента управления ObjectDataSource

После внесения этих изменений перейдите на эту страницу через браузер. Вы увидите 10 продуктов, упорядоченных в алфавитном порядке. Найдите минутку, чтобы выполнить пошаговое выполнение данных по одной странице за раз. Хотя между разбиением по умолчанию и пользовательским разбиением по страницам нет визуального различия с точки зрения конечного пользователя, пользовательское разбиение на страницы более эффективно использует большие объемы данных, так как оно извлекает только те записи, которые должны отображаться для данной страницы.

Данные, упорядоченные по названию продукта, разбиются на страницы с помощью пользовательского разбиения на страницы

Рис. 17. Данные, упорядоченные по названию продукта, отображаются на страницах с помощью пользовательской разбиения по страницам (щелкните для просмотра полноразмерного изображения)

Примечание

При пользовательском разбиении на страницы значение счетчика SelectCountMethod страниц, возвращаемое объектом ObjectDataSource, сохраняется в состоянии представления GridView. Другие переменные PageIndexGridView , , SelectedIndexEditIndexDataKeys коллекция и т. д. хранятся в состоянии элемента управления, которое сохраняется независимо от значения свойства GridViewEnableViewState. PageCount Так как значение сохраняется между обратными передачами с помощью состояния представления, при использовании интерфейса разбиения по страницам, содержащего ссылку на последнюю страницу, крайне важно включить состояние представления GridView. (Если интерфейс подкачки не содержит прямую ссылку на последнюю страницу, можно отключить состояние просмотра.)

Щелчок ссылки на последнюю страницу вызывает обратную передачу и указывает GridView обновить свое PageIndex свойство. Если щелкнуть ссылку на последнюю страницу, GridView присваивает своему PageIndex свойству значение, которое меньше его PageCount свойства. Если состояние представления отключено, PageCount значение теряется во время обратной PageIndex передачи, а вместо этого присваивается максимальное целочисленное значение. Затем GridView пытается определить индекс начальной строки путем умножения PageSize свойств и PageCount . Это приводит к , OverflowException так как продукт превышает максимально допустимый размер целого числа.

Реализация пользовательской разбиения по страницам и сортировки

Текущая реализация пользовательского разбиения на страницы требует, чтобы при создании GetProductsPaged хранимой процедуры был указан статический порядок разбиения данных на страницы. Однако вы могли заметить, что смарт-тег GridView содержит флажок Включить сортировку в дополнение к параметру Включить разбиение по страницам. К сожалению, добавление поддержки сортировки в GridView с текущей пользовательской реализацией разбиения на страницы будет сортировать только записи на просматриваемой странице данных. Например, если в GridView также настроена поддержка разбиения по страницам, а затем при просмотре первой страницы данных сортировка по названию продукта в порядке убывания будет отменен порядок продуктов на странице 1. Как показано на рисунке 18, в качестве первого продукта при сортировке в обратном алфавитном порядке показан карнарвон тигров, который игнорирует 71 другие продукты, которые приходят после Карнарвон Тигры, в алфавитном порядке; При сортировке учитываются только те записи, которые находятся на первой странице.

Сортируются только данные, отображаемые на текущей странице

Рис. 18. Сортируются только данные, отображаемые на текущей странице (щелкните для просмотра полноразмерного изображения)

Сортировка применяется только к текущей странице данных, так как сортировка выполняется после получения данных из метода BLL GetProductsPaged , и этот метод возвращает только эти записи для конкретной страницы. Для правильной реализации сортировки необходимо передать выражение GetProductsPaged сортировки в метод , чтобы данные можно было ранжировать соответствующим образом, прежде чем возвращать определенную страницу данных. Мы рассмотрим, как это сделать, в следующем руководстве.

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

Если включить функцию удаления в GridView, данные которого выстраивается с помощью пользовательских методов разбиения на страницы, вы обнаружите, что при удалении последней записи с последней страницы GridView исчезает, а не уменьшается соответствующим образом PageIndex. Чтобы воспроизвести эту ошибку, включите удаление для только что созданного руководства. Перейдите на последнюю страницу (страницу 9), где вы должны увидеть один продукт, так как мы разбиваем по страницам 81 продукт, 10 продуктов за раз. Удалите этот продукт.

После удаления последнего продукта GridView должен автоматически перейти на восьмую страницу, и такая функциональность будет реализована при разбиении по страницам по умолчанию. Однако при пользовательском разбиении на страницы после удаления последнего продукта на последней странице GridView просто полностью исчезает с экрана. Точная причина, по которой это происходит, выходит за рамки область этого руководства. Сведения об источнике этой проблемы см. в статье Удаление последней записи на последней странице из GridView с пользовательским разбиением на страницы. В целом это связано со следующей последовательностью действий, выполняемых GridView при нажатии кнопки Удалить:

  1. Удаление записи
  2. Получение соответствующих записей для отображения для указанных PageIndex и PageSize
  3. Убедитесь, что PageIndex объект не превышает количество страниц данных в источнике данных; в противном случае автоматически уменьшите значение свойства GridView PageIndex .
  4. Привязка соответствующей страницы данных к GridView с помощью записей, полученных на шаге 2

Проблема связана с тем, что на шаге 2 PageIndex при захвате записей для отображения по-прежнему PageIndex используется последняя страница, единственная запись которой была только что удалена. Таким образом, на шаге 2 записи не возвращаются, так как последняя страница данных больше не содержит записей. Затем на шаге 3 GridView понимает, что его PageIndex свойство больше, чем общее количество страниц в источнике данных (так как мы удалили последнюю запись на последней странице), и, следовательно, уменьшает его PageIndex свойство. На шаге 4 GridView пытается привязать себя к данным, извлеченным на шаге 2; однако на шаге 2 записи не были возвращены, поэтому gridView пустой. При разбиении по страницам по умолчанию эта проблема не возникает, так как на шаге 2 все записи извлекаются из источника данных.

Чтобы устранить эту проблему, у нас есть два варианта. Во-первых, необходимо создать обработчик событий для обработчика RowDeleted событий GridView, который определяет, сколько записей было отображено на только что удаленной странице. Если была только одна запись, то только что удаленная запись должна была быть последней, и нам нужно уменьшать GridView PageIndex. Конечно, мы хотим обновить только в том PageIndex случае, если операция удаления была выполнена успешно, что можно определить, убедив, что e.Exception свойство имеет значение null.

Этот подход работает, так как он обновляет PageIndex после шага 1, но до шага 2. Таким образом, на шаге 2 возвращается соответствующий набор записей. Для этого используйте следующий код:

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 RowDeleted и задать AffectedRows для свойства значение 1. После удаления записи на шаге 1 (но перед повторным извлечением данных на шаге 2) GridView обновляет свое PageIndex свойство, если операция повлияла на одну или несколько строк. Однако AffectedRows свойство не задается ObjectDataSource, поэтому этот шаг опущен. Один из способов выполнения этого шага — вручную задать свойство , AffectedRows если операция удаления завершится успешно. Это можно сделать с помощью следующего кода:

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 части примера.

Сравнение производительности по умолчанию и настраиваемой разбиения по страницам

Так как настраиваемое разбиение на страницы получает только необходимые записи, а разбиение по умолчанию возвращает все записи для каждой просматриваемой страницы, очевидно, что настраиваемое разбиение по страницам эффективнее, чем разбиение по умолчанию. Но насколько эффективнее пользовательское разбиение по страницам? Какой тип повышения производительности можно увидеть, перейдя от разбиения по умолчанию на пользовательские разбиения по страницам?

К сожалению, нет одного размера подходит для всех ответ здесь. Повышение производительности зависит от ряда факторов, наиболее важными из которых являются количество записей, которые передаются на страницы, а также нагрузка на сервер базы данных и каналы связи между веб-сервером и сервером базы данных. Для небольших таблиц с несколькими десятками записей разница в производительности может быть незначительной. Однако для больших таблиц с тысячами и сотнями тысяч строк разница в производительности является острой.

Моя статья "Пользовательское разбиение по страницам в ASP.NET 2.0 с SQL Server 2005" содержит некоторые тесты производительности, которые я побежал, чтобы продемонстрировать различия в производительности между этими двумя методами разбиения по страницам при разбиении по страницам в таблице базы данных с 50 000 записей. В этих тестах я изучил время выполнения запроса на уровне SQL Server (с помощью SQL Profiler) и на странице ASP.NET с помощью функций трассировки ASP.NET. Имейте в виду, что эти тесты выполнялись в поле разработки с одним активным пользователем и поэтому являются ненаучными и не имитируют типичные шаблоны загрузки веб-сайта. Независимо от этого, результаты показывают относительные различия во времени выполнения для по умолчанию и настраиваемого разбиения по страницам при работе с достаточно большими объемами данных.

Средняя длительность (с) Reads
Профилировщик SQL подкачки по умолчанию 1.411 383
Настраиваемый профилировщик SQL подкачки 0.002 29
Трассировка ASP.NET разбиения по умолчанию 2.379 Н/Д
Настраиваемая трассировка ASP.NET разбиения по страницам 0.029 Н/Д

Как видите, для получения определенной страницы данных требуется в среднем 354 операций чтения и завершения за долю времени. На странице ASP.NET пользовательская страница смогла отрисовыться в течение почти 1/100 й части времени, необходимого при использовании разбиения по страницам по умолчанию.

Сводка

Разбиение на страницы по умолчанию — это cinch для реализации только проверка флажок Включить разбиение на страницы в смарт-теге веб-элемента управления данными, но такая простота за счет производительности. При разбиении на страницы по умолчанию, когда пользователь запрашивает любую страницу данных, возвращаются все записи, даже если может отображаться лишь небольшая их часть. Для борьбы с этой нагрузкой на производительность ObjectDataSource предлагает альтернативный вариант разбиения по страницам.

Хотя настраиваемое разбиение по страницам улучшает производительность по умолчанию, извлекая только те записи, которые должны отображаться, реализация настраиваемого разбиения на разбиение на разбиение на другой сайт более задействуется. Во-первых, необходимо написать запрос, который правильно (и эффективно) обращается к определенному подмножество запрошенных записей. Это можно сделать несколькими способами; Мы рассмотрели в этом руководстве использование новой ROW_NUMBER() функции SQL Server 2005 для ранжирования результатов, а затем для возврата только тех результатов, ранжирование которых находится в указанном диапазоне. Кроме того, нам нужно добавить средства для определения общего количества записей, которые будут выстраивать на страницы. После создания этих методов DAL и BLL необходимо также настроить ObjectDataSource, чтобы он смог определить, сколько записей выстраиваются на страницу, и правильно передать значения индекса начальной строки и максимального числа строк в BLL.

Хотя реализация пользовательского разбиения по страницам требует ряда шагов и не так проста, как разбиение по умолчанию, настраиваемое разбиение по страницам является необходимостью при разбиении по страницам достаточно больших объемов данных. Как показали результаты, пользовательская разбиение на страницы может сбрасывать секунды от времени отрисовки ASP.NET страницы и может облегчить нагрузку на сервер базы данных на один или несколько порядков.

Счастливое программирование!

Об авторе

Скотт Митчелл( Scott Mitchell), автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часах. Он может быть доступен в mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.