Сортировка GridView
84ASP.NET --- Основы ASP.NET --- Сортировка GridView<
Средства сортировки GridView позволяют переупорядочить результирующий набор строк GridView, щелкая на заголовке столбца. Это удобно, и это легко реализовать.
Чтобы разрешить сортировку, нужно установить свойство GridView.AllowSorting в true. Далее понадобится определить SortExpression для каждого столбца, который может быть отсортирован. Теоретически выражение сортировки может использовать любой синтаксис, который понимает элемент управления источником данных. На практике выражение сортировки почти всегда принимает форму, используемую в конструкции ORDER BY запроса SQL. Это значит, что выражение сортировки может включать единственное поле или список полей, разделенный запятыми, и с необязательным словом ASC или DESC, добавленным после имени столбца, которое позволяет сортировать в восходящем или нисходящем порядке.
Вот как определить столбец FirstName, чтобы строки сортировались в алфавитном порядке по имени:
<asp:BoundField DataField="FirstName" HeaderText="FirstName"
SortExpression="FirstName" />
Если два раза щелкнуть на заголовке столбца FirstName в строке, то первый щелчок отсортирует его по алфавиту в прямом порядке, а второй — в обратном. В случае использования автоматически сгенерированных столбцов каждый привязанный столбец имеет свойство SortExpression, совпадающее с его свойством DataField. Если не хотите, чтобы столбец был сортируемым, не устанавливайте свойство SortExpression.
После того, как выражение сортировки ассоциировано со столбцом и свойство AllowSorting установлено в true, элемент GridView будет отображать заголовки как активные ссылки. Однако реализация действительной логики сортировки остается делом элемента управления источником данных. Как именно реализована сортировка - зависит от используемого источника данных. Не все источники данных поддерживают сортировку, но SqlDataSource и ObjectDataSource это делают.
Сортировка с помощью SqlDataSource
В случае SqlDataSource сортировка производится встроенными средствами класса DataView. По сути, когда пользователь щелкает на ссылке столбца, свойство DataView.Sort устанавливается равным выражению сортировки по этому столбцу.
Как объяснялось ранее, каждый объект DataTable связан с DataView по умолчанию. DataView - это "окно" в DataTable, которое позволяет применять сортировку и фильтрацию без изменения структуры лежащей в основе таблицы. DataView можно использовать программно, но когда вы применяете SqlDataSource, он используется неявно, "за кулисами". Однако элемент DataView доступен только тогда, когда свойство DataSourceMode установлено в SqlDataSourceMode.DataSet.
При сортировке DataView данные из базы извлекаются неупорядоченными, а результат сортируется в памяти. Это не самый скоростной подход (сортировка в памяти требует больше накладных расходов и выполняется медленнее, чем это делает сам SQL Server), но она лучше масштабируется, когда вы добавляете кэширование. Дело в том, что можно кэшировать единственную копию данных и сортировать ее динамически несколькими различными способами. Без сортировки DataView для извлечения вновь отсортированных данных должен быть выполнен отдельный запрос.
На рисунке ниже показан сортируемый GridView со столбцами-ссылками. Обратите внимание, что никакого пользовательского кода в этом сценарии не требуется:
Сортировка соответствует типу данных столбца. Числовые столбцы и столбцы типа даты упорядочиваются от меньшего к большему значению. Строковые столбцы сортируются в алфавитно-цифровом порядке, независимо от регистра, предполагая, что лежащее в основе свойство DataTable.CaseSensitive равно false (значение по умолчанию). Столбцы, которые содержат двоичные данные, не могут быть отсортированы.
Сортировка с помощью ObjectDataSource
Элемент ObjectDataSource предлагает на выбор два варианта:
Если метод выборки возвращает DataSet или DataTable, ObjectDataSource может использовать ту же автоматическую сортировку, что и SqlDataSource.
Если метод выборки возвращает пользовательскую коллекцию, то вы должны предоставить метод выборки, принимающий выражение сортировки и выполняющий ее. Опять-таки, это поведение обеспечивает достаточную гибкость в построении решений, но они не обязательно будут идеальными. Например, вместо создания метода GetEmployees(), способного выполнять сортировку, может быть больше смысла создать пользовательский класс коллекции EmployeeDetails с методом Sort(). К сожалению, ObjectDataSource не поддерживает такой шаблон.
Чтобы использовать параметр сортировки, необходимо написать метод выборки, принимающий одиночный строковый параметр. Затем понадобится установить свойство SortParameterName для идентификации имени этого параметра:
<asp:ObjectDataSource ID="sourceEmployeesODS" runat="server"
TypeName="EmployeeDB" SelectMethod="GetEmployees" SortParameterName="sortExpression" />
В случае установки SetParameterName элемент ObjectDataSource всегда будет вызывать версию вашего метода, которая принимает выражение сортировки. Если данные не сортированы (например, при первоначальном заполнении сетки), то ObjectDataSource просто передает пустую строку вместо выражения сортировки.
Теперь необходимо реализовать метод GetEmployees() принимающий параметр сортировки в классе EmployeeDB и решить, как осуществлять сортировку. Легче всего заполнить автономный объект DataSet, так что можно положиться на функциональность сортировки DataView. Рассмотрим пример метода GetEmployees() в компоненте базы данных, выполняющего сортировку описанным способом:
public List GetEmployees(string sortExpression)
{
SqlConnection con = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("GetAllEmployees", con);
cmd.CommandType = CommandType.StoredProcedure;
// Создать коллекцию для всех записей о сотрудниках
List<EmployeeDetails> employees = new List<EmployeeDetails>();
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataSet dataset = new DataSet();
try
{
con.Open();
adapter.Fill(dataset, "Employee");
}
catch (SqlException)
{
throw new ApplicationException("Ошибка данных");
}
finally
{
con.Close();
}
// Применить сортировку
DataView view = dataset.Tables["Employee"].DefaultView;
view.Sort = sortExpression;
foreach (DataRowView row in view)
{
EmployeeDetails emp = new EmployeeDetails(
(int)row["EmployeeID"], (string)row["FirstName"],
(string)row["LastName"], (DateTime)row["BirthDate"]);
employees.Add(emp);
}
return employees;
}
Другой подход предусматривает изменение действительного выполняемого запроса в соответствии с выражением сортировки. В результате сортировку будет проводить база данных. Этот подход несколько сложнее, но безупречного выбора нет. Вот две наиболее часто используемые возможности:
Можно динамически построить оператор SQL с конструкцией ORDER BY. Однако это порождает опасность атаки внедрением SQL, если только вы не будете тщательно проверять ввод.
Можно написать условную логику для проверки выражения сортировки и выполнения различных запросов соответственно (либо в методе выборки, либо в хранимой процедуре). Этот код, скорее всего, получится довольно хрупким и потребует реализации сложного анализа и разбора строк.
Сортировка и выбор
В случае одновременного применения выбора и сортировки вы столкнетесь с еще одной сложностью. Чтобы увидеть эту проблему воочию, выберите строку, затем отсортируйте данные по любому столбцу. Вы увидите, что выбор останется, но переместится на другой элемент, который после сортировки будет иметь тот же индекс, что и выбранная строка до сортировки. Другими словами, если вы выбираете вторую строку и выполняете сортировку, то вторая строка останется выбранной на новой странице, несмотря на то, что это будет уже другая строка.
В прошлом единственным решением этой проблемы было программное изменение выбранного элемента при каждом выполнении пользователем щелчка на ссылке заголовка. Но теперь в ASP.NET появилось свойство GridView.EnablePersistedSelection, которое позволяет быстрее справиться с ней. Просто установите это свойство в true, и ASP.NET будет заботиться о том, чтобы выбранный элемент идентифицировался по его ключу данных. Благодаря этому, выбранным будет оставаться нужный элемент, даже в случае его перемещения в новую позицию после сортировки GridView.
Расширенная сортировка
Сортировка GridView достаточно проста — здесь она поддерживается по любому сортируемому столбцу в порядке возрастания и убывания. Но в некоторых приложениях пользователям предлагаются большие возможности сортировки, и они могут упорядочивать огромные результирующие наборы посредством более сложных сортирующих выражений.
Первое, что можно сделать в плане совершенствования сортировки в GridView — обработать событие GridView.Sorting, которое происходит непосредственно перед применением сортировки. В этот момент выражение сортировки можно изменять. Например, можно использовать ту же логику для составной сортировки при щелчках на разных столбцах. Скажем, можно проверить, щелкнул ли пользователь сначала на LastName, а затем на FirstName. В этом случае имеет смысл применить сортировку по LastName + FirstName:
protected void gridEmployees_Sorting(object sender, GridViewSortEventArgs e)
{
if (e.SortExpression == "FirstName" && gridEmployees.SortExpression == "LastName")
{
// На основе текущей сортировки и запрошенной
// строится выражение для составной сортировки
e.SortExpression = "LastName, FirstName";
}
}
Этот подход к сортировке можно еще более усовершенствовать для каскадной сортировки по произвольной коллекции столбцов, сохраняя предыдущие пользовательские выборы сортировки в видимом состоянии, и используя их для построения сложных сортирующих выражений. Конечно, важно не злоупотреблять этим, создавая собственные механизмы сортировки, которые будут непонятны пользователям — это создаст больше проблем, чем позволит решить.
В распоряжении разработчиков имеется еще один прием. Можно сортировать GridView программно, вызывая метод GridView.Sort() и применяя выражение сортировки. Это может быть удобно, если вы хотите предварительно отсортировать длинный отчет перед тем, как предоставить его пользователю. Это также имеет смысл, если нужно разрешить пользователю выбирать из списка предопределенных вариантов сортировки (представленного в другом элементе управления) вместо щелчков на заголовках столбцов.
На рисунке ниже представлен пример:
Когда элемент выбирается в списке, выполняется сортировка с помощью следующего кода:
<form id="form1" runat="server">
<asp:SqlDataSource ID="getEmployeesSDS" runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="SELECT EmployeeID, LastName, FirstName, BirthDate, City FROM Employees" />
Выберите тип сортировки: <asp:DropDownList ID="lstSorts" runat="server"
Font-Names="Verdana" AutoPostBack="True" OnSelectedIndexChanged="lstSorts_SelectedIndexChanged" Width="309px">
<asp:ListItem>EmployeeID</asp:ListItem>
<asp:ListItem>LastName, FirstName</asp:ListItem>
<asp:ListItem>BirthDate, FirstName, LastName</asp:ListItem>
</asp:DropDownList><br /><br />
<asp:GridView ID="gridEmployees" runat="server" DataSourceID="getEmployeesSDS" DataKeyNames="EmployeeID"
BackColor="White" BorderColor="#336666" BorderStyle="Double" BorderWidth="3px"
CellPadding="6" GridLines="Horizontal" AutoGenerateColumns="False"
AllowSorting="true" OnSorting="gridEmployees_Sorting" EnablePersistedSelection="true">
<Columns>
<asp:ButtonField DataTextField="EmployeeID" ButtonType="Button" CommandName="Select" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
<asp:BoundField DataField="BirthDate" HeaderText="BirthDate" DataFormatString="{0:dd/MM/yyyy}"
SortExpression="BirthDate" />
</Columns>
<HeaderStyle BackColor="#336666" Font-Bold="True" ForeColor="White" />
<RowStyle BackColor="White" ForeColor="#333333" />
<SelectedRowStyle BackColor="#339966" Font-Bold="True" ForeColor="White" />
</asp:GridView>
</form>
// Обработчики
protected void lstSorts_SelectedIndexChanged(object sender, EventArgs e)
{
gridEmployees.Sort(lstSorts.SelectedValue, SortDirection.Ascending);
}
protected void gridEmployees_Sorting(object sender, GridViewSortEventArgs e)
{
if (e.SortExpression == "FirstName" && gridEmployees.SortExpression == "LastName")
{
// На основе текущей сортировки и запрошенной
// строится выражение для составной сортировки
e.SortExpression = "LastName, FirstName";
}
}