Упорядочение нестандартно разбитых по страницам данных (C#)
В предыдущем руководстве мы узнали, как реализовать настраиваемое разбиение на страницы при представлении данных на веб-странице. В этом руководстве мы посмотрим, как расширить предыдущий пример, чтобы включить поддержку сортировки пользовательского разбиения по страницам.
Введение
По сравнению с разбиением по страницам по умолчанию пользовательская подкачки может повысить производительность разбиения по страницам на несколько порядков, что делает пользовательское разбиение на разбиение по страницам де-факто при разбиении по страницам с большими объемами данных. Однако реализация пользовательского разбиения по страницам является более важной, чем реализация разбиения по умолчанию, особенно при добавлении сортировки в набор. В этом руководстве мы расширим пример из предыдущего, чтобы включить поддержку сортировки и настраиваемого разбиения по страницам.
Примечание
Так как в этом руководстве используется предыдущий, прежде чем начать, скопируйте декларативный синтаксис элемента <asp:Content>
с веб-страницы предыдущего руководства (EfficientPaging.aspx
) и вставьте его между <asp:Content>
элементом на SortParameter.aspx
странице. Более подробное описание репликации функций одной ASP.NET страницы на другую см. в шаге 1 руководства Добавление элементов управления проверкой в интерфейсы редактирования и вставки .
Шаг 1. Повторное определение пользовательского метода разбиения по страницам
Для правильной работы пользовательского разбиения по страницам необходимо реализовать некоторый метод, который может эффективно захватывать определенное подмножество записей с учетом параметров Индекс начальной строки и Максимальное число строк. Для достижения этой цели можно использовать несколько методов. В предыдущем руководстве мы рассмотрели эту задачу с помощью новой ROW_NUMBER()
функции ранжирования Microsoft SQL Server 2005. Короче говоря, ранжирующая функция присваивает номер строки каждой строке, ROW_NUMBER()
возвращаемой запросом, который ранжируется по указанному порядку сортировки. Затем соответствующее подмножество записей получается путем возврата определенного раздела нумеруемых результатов. В следующем запросе показано, как использовать этот метод для возврата этих продуктов с номерами от 11 до 20 при ранжировании результатов, упорядоченных по алфавиту ProductName
по :
SELECT ProductID, ProductName, ...
FROM
(SELECT ProductID, ProductName, ..., ROW_NUMBER() OVER
(ORDER BY ProductName) AS RowRank
FROM Products) AS ProductsWithRowNumbers
WHERE RowRank > 10 AND RowRank <= 20
Этот метод хорошо подходит для разбиения по страницам с использованием определенного порядка сортировки (ProductName
в данном случае с сортировкой по алфавиту), но запрос необходимо изменить, чтобы отобразить результаты, отсортированные по другому выражению сортировки. В идеале приведенный выше запрос можно переписать для использования параметра в предложении OVER
следующим образом:
SELECT ProductID, ProductName, ...
FROM
(SELECT ProductID, ProductName, ..., ROW_NUMBER() OVER
(ORDER BY @sortExpression) AS RowRank
FROM Products) AS ProductsWithRowNumbers
WHERE RowRank > 10 AND RowRank <= 20
К сожалению, параметризованные ORDER BY
предложения не допускаются. Вместо этого необходимо создать хранимую процедуру, которая принимает входной @sortExpression
параметр, но использует одно из следующих обходных решений:
- Написание жестко заданных запросов для каждого из выражений сортировки, которые могут использоваться; Затем используйте
IF/ELSE
инструкции T-SQL, чтобы определить, какой запрос следует выполнить. - Используйте инструкцию для
CASE
предоставления динамическихORDER BY
выражений на@sortExpressio
основе входного параметра n. Дополнительные сведения см. в разделе Используется для динамической сортировки результатов запроса статьи Инструкции T-SQLCASE
. - Создайте соответствующий запрос как строку в хранимой процедуре, а затем используйте
sp_executesql
системную хранимую процедуру для выполнения динамического запроса.
Каждый из этих обходных путей имеет некоторые недостатки. Первый вариант не так поддерживается, как два других, так как требует создания запроса для каждого возможного выражения сортировки. Поэтому, если позже вы решите добавить в GridView новые сортируемые поля, вам также потребуется вернуться и обновить хранимую процедуру. Второй подход имеет некоторые тонкости, которые вызывают проблемы с производительностью при сортировке по нестроковым столбцам базы данных, а также страдают от проблем с удобством обслуживания, что и первый. Третий вариант, использующий динамический SQL, создает риск атаки путем внедрения кода SQL, если злоумышленник может выполнить хранимую процедуру, передав значения входных параметров по своему выбору.
Хотя ни один из этих подходов не является совершенным, я думаю, третий вариант является лучшим из трех. Благодаря использованию динамического SQL он обеспечивает уровень гибкости двух других. Кроме того, атакой путем внедрения кода SQL можно воспользоваться только в том случае, если злоумышленник может выполнить хранимую процедуру, передав входные параметры по своему выбору. Так как DAL использует параметризованные запросы, ADO.NET защитит те параметры, которые отправляются в базу данных через архитектуру. Это означает, что уязвимость атаки путем внедрения кода SQL существует только в том случае, если злоумышленник может напрямую выполнить хранимую процедуру.
Чтобы реализовать эту функцию, создайте в базе данных Northwind новую хранимую процедуру с именем GetProductsPagedAndSorted
. Эта хранимая процедура должна принимать три входных параметра: @sortExpression
, входной параметр типа nvarchar(100
), который указывает способ сортировки результатов и внедряется непосредственно после ORDER BY
текста в OVER
предложении; и @startRowIndex
@maximumRows
, те же два целочисленных входных параметра из GetProductsPaged
хранимой процедуры, рассмотренной в предыдущем руководстве. Создайте хранимую GetProductsPagedAndSorted
процедуру с помощью следующего скрипта:
CREATE PROCEDURE dbo.GetProductsPagedAndSorted
(
@sortExpression nvarchar(100),
@startRowIndex int,
@maximumRows int
)
AS
-- Make sure a @sortExpression is specified
IF LEN(@sortExpression) = 0
SET @sortExpression = 'ProductID'
-- Issue query
DECLARE @sql nvarchar(4000)
SET @sql = 'SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
CategoryName, SupplierName
FROM (SELECT ProductID, ProductName, p.SupplierID, p.CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued,
c.CategoryName, s.CompanyName AS SupplierName,
ROW_NUMBER() OVER (ORDER BY ' + @sortExpression + ') AS RowRank
FROM Products AS p
INNER JOIN Categories AS c ON
c.CategoryID = p.CategoryID
INNER JOIN Suppliers AS s ON
s.SupplierID = p.SupplierID) AS ProductsWithRowNumbers
WHERE RowRank > ' + CONVERT(nvarchar(10), @startRowIndex) +
' AND RowRank <= (' + CONVERT(nvarchar(10), @startRowIndex) + ' + '
+ CONVERT(nvarchar(10), @maximumRows) + ')'
-- Execute the SQL query
EXEC sp_executesql @sql
Хранимая процедура начинается с проверки того, что для параметра задано значение @sortExpression
. Если он отсутствует, результаты ранжируются по ProductID
. Затем создается динамический SQL-запрос. Обратите внимание, что динамический SQL-запрос здесь немного отличается от предыдущих запросов, используемых для получения всех строк из таблицы Products. В предыдущих примерах мы получали все связанные категории продуктов и имена поставщиков с помощью вложенного запроса. Это решение было принято еще в учебнике Создание уровня доступа к данным и было сделано вместо использования JOIN
s, так как TableAdapter не может автоматически создать связанные методы вставки, обновления и удаления для таких запросов. Однако хранимая GetProductsPagedAndSorted
процедура должна использовать JOIN
для упорядочения результатов по категориям или именам поставщиков.
Этот динамический запрос создается путем объединения частей статического запроса и @sortExpression
параметров , @startRowIndex
и @maximumRows
. Так как @startRowIndex
и @maximumRows
являются целочисленными параметрами, для правильного объединения их необходимо преобразовать в nvarchars. После создания этого динамического SQL-запроса он выполняется с помощью sp_executesql
.
Проверьте эту хранимую процедуру с разными значениями для @sortExpression
параметров , @startRowIndex
и @maximumRows
. В Обозреватель сервер щелкните правой кнопкой мыши имя хранимой процедуры и выберите команду Выполнить. Откроется диалоговое окно Запуск хранимой процедуры, в котором можно ввести входные параметры (см. рис. 1). Чтобы отсортировать результаты по имени категории, используйте CategoryName для @sortExpression
значения параметра; для сортировки по названию компании поставщика используйте Параметр CompanyName. После указания значений параметров нажмите кнопку ОК. Результаты отображаются в окне Вывод. На рисунке 2 показаны результаты при возврате продуктов с 11 по 20 при заказе UnitPrice
по убыванию.
Рис. 1. Попробуйте использовать разные значения для трех входных параметров хранимой процедуры
Рис. 2. Результаты хранимой процедуры отображаются в окне вывода (щелкните для просмотра полноразмерного изображения)
Примечание
При ранжировании результатов по указанному ORDER BY
столбцу в предложении OVER
SQL Server должны отсортировать результаты. Это быстрая операция, если есть кластеризованный индекс по столбцам, по которым упорядочены результаты, или если имеется покрывающий индекс, но в противном случае это может быть более дорогостоящим. Чтобы повысить производительность для достаточно больших запросов, рассмотрите возможность добавления некластинного индекса для столбца, по которому упорядочены результаты. Дополнительные сведения см. в статье Ранжирование функций и производительности в SQL Server 2005 г.
Шаг 2. Расширение уровней доступа к данным и бизнес-логики
После создания хранимой GetProductsPagedAndSorted
процедуры наш следующий шаг — предоставить средства для выполнения этой хранимой процедуры с помощью нашей архитектуры приложения. Это влечет за собой добавление соответствующего метода как к DAL, так и к BLL. Начнем с добавления метода в DAL. Откройте типизированный Northwind.xsd
набор данных, щелкните правой ProductsTableAdapter
кнопкой мыши элемент и выберите пункт Добавить запрос в контекстном меню. Как и в предыдущем руководстве, мы хотим настроить этот новый метод DAL для использования существующей хранимой процедуры — GetProductsPagedAndSorted
, в данном случае. Начните с указания, что новый метод TableAdapter будет использовать существующую хранимую процедуру.
Рис. 3. Выбор использования существующей хранимой процедуры
Чтобы указать используемую хранимую процедуру, выберите хранимую GetProductsPagedAndSorted
процедуру из раскрывающегося списка на следующем экране.
Рис. 4. Использование хранимой процедуры GetProductsPagedAndSorted
Эта хранимая процедура возвращает набор записей в качестве результатов, поэтому на следующем экране укажите, что она возвращает табличные данные.
Рис. 5. Указание на то, что хранимая процедура возвращает табличные данные
Наконец, создайте методы DAL, использующие шаблоны Fill a DataTable и Return a DataTable, назвав методы FillPagedAndSorted
и GetProductsPagedAndSorted
соответственно.
Рис. 6. Выбор имен методов
Теперь, когда мы расширили DAL, мы готовы обратиться к BLL. ProductsBLL
Откройте файл класса и добавьте новый метод GetProductsPagedAndSorted
. Этот метод должен принимать три входных параметра sortExpression
, startRowIndex
и , и maximumRows
должен просто вызывать метод DAL GetProductsPagedAndSorted
, как показано ниже:
[System.ComponentModel.DataObjectMethodAttribute(
System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsPagedAndSorted(
string sortExpression, int startRowIndex, int maximumRows)
{
return Adapter.GetProductsPagedAndSorted
(sortExpression, startRowIndex, maximumRows);
}
Шаг 3. Настройка ObjectDataSource для передачи параметра SortExpression
После добавления методов, использующих GetProductsPagedAndSorted
хранимую процедуру, DAL и BLL, остается настроить ObjectDataSource на SortParameter.aspx
странице для использования нового метода BLL и передать параметр на SortExpression
основе столбца, по которому пользователь запросил отсортировать результаты.
Для начала измените objectDataSource SelectMethod
с GetProductsPaged
на GetProductsPagedAndSorted
. Это можно сделать с помощью мастера настройки источника данных, из окно свойств или непосредственно с помощью декларативного синтаксиса. Далее необходимо указать значение для свойства ObjectDataSourceSortParameterName
. Если это свойство задано, ObjectDataSource пытается передать свойство GridView SortExpression
в SelectMethod
. В частности, ObjectDataSource ищет входной параметр, имя которого равно значению SortParameterName
свойства . Так как метод BLL GetProductsPagedAndSorted
имеет входной параметр выражения сортировки с именем sortExpression
, задайте для свойства ObjectDataSource значение SortExpression
sortExpression .
После внесения этих двух изменений декларативный синтаксис ObjectDataSource должен выглядеть примерно так:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
SelectMethod="GetProductsPagedAndSorted" EnablePaging="True"
SelectCountMethod="TotalNumberOfProducts" SortParameterName="sortExpression">
</asp:ObjectDataSource>
Примечание
Как и в предыдущем руководстве, убедитесь, что ObjectDataSource не включает входные параметры sortExpression, startRowIndex или maximumRows в коллекцию SelectParameters.
Чтобы включить сортировку в GridView, просто проверка флажок Включить сортировку в смарт-теге GridView, который задает свойству GridView AllowSorting
значение true
и приводит к отображению текста заголовка для каждого столбца в виде LinkButton. Когда пользователь щелкает один из заголовков LinkButtons, выполняется обратная связь и выполняется следующее:
- GridView обновляет свое
SortExpression
свойство значениемSortExpression
поля, ссылка на заголовок которого была щелкнуна. - ObjectDataSource вызывает метод BLL
GetProductsPagedAndSorted
, передавая свойство GridViewSortExpression
в качестве значения входного параметра методаsortExpression
(вместе со значениями соответствующихstartRowIndex
иmaximumRows
входных параметров). - BLL вызывает метод DAL s
GetProductsPagedAndSorted
. - DAL выполняет хранимую
GetProductsPagedAndSorted
процедуру, передавая@sortExpression
параметр (вместе со значениями@startRowIndex
входных параметров и@maximumRows
). - Хранимая процедура возвращает соответствующее подмножество данных в BLL, который возвращает их в ObjectDataSource; Затем эти данные привязывается к GridView, отрисовывается в HTML и отправляется конечному пользователю.
На рисунке 7 показана первая страница результатов при сортировке UnitPrice
по в порядке возрастания.
Рис. 7. Результаты сортируются по unitPrice (щелкните для просмотра полноразмерного изображения)
Хотя текущая реализация может правильно сортировать результаты по названию продукта, названию категории, количеству на единицу и цене за единицу, попытка упорядочить результаты по имени поставщика приводит к исключению среды выполнения (см. рис. 8).
Рис. 8. Попытка отсортировать результаты поставщиком приводит к следующему исключению среды выполнения
Это исключение возникает из-за того, SortExpression
что для объекта BoundField GridView SupplierName
задано значение SupplierName
. Однако имя поставщика в Suppliers
таблице на самом деле называется CompanyName
. Мы получили псевдоним этого столбца как SupplierName
. Однако предложение, используемое функциейROW_NUMBER()
, OVER
не может использовать псевдоним и должно использовать фактическое имя столбца. Поэтому измените boundField SupplierName
s SortExpression
с SupplierName на CompanyName (см. рис. 9). Как показано на рисунке 10, после этого изменения результаты могут быть отсортированы поставщиком.
Рис. 9. Изменение SortExpression Для Параметра BoundField поставщика на CompanyName
Рис. 10. Результаты теперь можно сортировать по поставщику (щелкните, чтобы просмотреть полноразмерное изображение)
Сводка
Реализация пользовательской разбиения на разбиение на разбиения на разбиение требуется указать порядок сортировки результатов во время разработки. Короче говоря, это означало, что реализованная нами пользовательская реализация разбиения по страницам не могла одновременно предоставлять возможности сортировки. В этом руководстве мы преодолели это ограничение, расширив хранимую процедуру с первого, чтобы включить входной @sortExpression
параметр, по которому можно отсортировать результаты.
После создания этой хранимой процедуры и создания новых методов в DAL и BLL мы смогли реализовать GridView, который предлагает как сортировку, так и настраиваемую разбиение по страницам, настроив ObjectDataSource для передачи текущего SortExpression
свойства GridView в BLL SelectMethod
.
Счастливое программирование!
Об авторе
Скотт Митчелл (Scott Mitchell), автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с Веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часа. Его можно связать по адресу mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.
Отдельная благодарность
Эта серия учебников была проверена многими полезными рецензентами. Ведущим рецензентом этого руководства был Карлос Сантос. Хотите ознакомиться с моими предстоящими статьями MSDN? Если да, опустите мне строку в mitchell@4GuysFromRolla.com.