Руководство по добавлению сортировки, фильтрации и разбиения по страницам с помощью Entity Framework в ASP.NET приложении MVC

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

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

Students_Index_page_with_paging

В этом учебнике рассмотрены следующие задачи.

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

Предварительные требования

Чтобы добавить сортировку на страницу Индекса учащегося, измените Index метод контроллера Student и добавьте код в Student представление Индекс.

Добавление функции сортировки в метод Index

  • В файле Controllers\StudentController.cs замените Index метод следующим кодом:

    public ActionResult Index(string sortOrder)
    {
       ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
       ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
       var students = from s in db.Students
                      select s;
       switch (sortOrder)
       {
          case "name_desc":
             students = students.OrderByDescending(s => s.LastName);
             break;
          case "Date":
             students = students.OrderBy(s => s.EnrollmentDate);
             break;
          case "date_desc":
             students = students.OrderByDescending(s => s.EnrollmentDate);
             break;
          default:
             students = students.OrderBy(s => s.LastName);
             break;
       }
       return View(students.ToList());
    }
    

Этот код принимает параметр sortOrder из строки запроса в URL. Значение строки запроса предоставляется ASP.NET MVC в качестве параметра метода действия. Параметр представляет собой строку , которая имеет значение "Name" или "Date", за которой при необходимости следует символ подчеркивания и строка "desc" для указания порядка убывания. По умолчанию используется порядок сортировки по возрастанию.

При первом запросе страницы Index строка запроса отсутствует. Учащиеся отображаются в порядке возрастания по , что является значением LastNameпо умолчанию, установленным регистром fall-through в операторе switch . Когда пользователь щелкает гиперссылку заголовка столбца, в строку запроса подставляется соответствующее значение параметра sortOrder.

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

ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

Это тернарные условные операторы. Первый из них указывает, что если sortOrder параметр имеет значение NULL или пустой, ViewBag.NameSortParm ему должно быть присвоено значение "name_desc", в противном случае ему должна быть присвоена пустая строка. Следующие два оператора устанавливают гиперссылки в заголовках столбцов в представлении следующим образом:

Текущий порядок сортировки Гиперссылка "Last Name" (Фамилия) Гиперссылка "Date" (Дата)
"Last Name" (Фамилия) по возрастанию descending ascending
"Last Name" (Фамилия) по убыванию ascending ascending
"Date" (Дата) по возрастанию ascending descending
"Date" (Дата) по убыванию ascending ascending

Метод использует LINQ to Entities для указания столбца для сортировки. Код создает переменную IQueryable<T> перед оператором switch , изменяет ее в операторе switch и вызывает ToList метод после switch оператора . После создания и изменения переменных IQueryable запрос в базу данных не отправляется. Запрос не выполняется до тех пор, пока объект не преобразуется IQueryable в коллекцию путем вызова метода, ToListнапример . Таким образом, этот код приводит к одному запросу, который не выполняется до выполнения инструкции return View .

В качестве альтернативы написанию различных инструкций LINQ для каждого порядка сортировки можно динамически создать инструкцию LINQ. Сведения о динамическом LINQ см. в разделе Dynamic LINQ.

  1. В views\Student\Index.cshtml замените <tr> элементы и <th> для строки заголовка выделенным кодом:

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm })
            </th>
            <th>First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm })
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
    

    Этот код использует сведения в свойствах ViewBag для настройки гиперссылок с соответствующими значениями строки запроса.

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

    Щелкнув заголовок Фамилия , учащиеся отображаются в порядке убывания фамилии.

Чтобы добавить фильтрацию на страницу индекса Students, добавьте текстовое поле и кнопку отправки в представление и внесите соответствующие изменения в Index метод . Текстовое поле позволяет ввести строку для поиска в полях имени и фамилии.

Добавление функций фильтрации в метод Index

  • В файле Controllers\StudentController.cs замените Index метод следующим кодом (изменения выделены):

    public ViewResult Index(string sortOrder, string searchString)
    {
        ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
        ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
        var students = from s in db.Students
                       select s;
        if (!String.IsNullOrEmpty(searchString))
        {
            students = students.Where(s => s.LastName.Contains(searchString)
                                   || s.FirstMidName.Contains(searchString));
        }
        switch (sortOrder)
        {
            case "name_desc":
                students = students.OrderByDescending(s => s.LastName);
                break;
            case "Date":
                students = students.OrderBy(s => s.EnrollmentDate);
                break;
            case "date_desc":
                students = students.OrderByDescending(s => s.EnrollmentDate);
                break;
            default:
                students = students.OrderBy(s => s.LastName);
                break;
        }
    
        return View(students.ToList());
    }
    

Код добавляет searchString параметр в Index метод . Значение строки поиска получается из текстового поля, которое мы добавили в представление Index. Он также добавляет where предложение в инструкцию LINQ, которая выбирает только учащихся, имя или фамилия которых содержит строку поиска. Оператор, добавляющий предложение, Where выполняется только при наличии значения для поиска.

Примечание

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

Например, реализация Contains метода платформа .NET Framework возвращает все строки при передаче пустой строки, но поставщик Entity Framework для SQL Server Compact 4.0 возвращает ноль строк для пустых строк. Поэтому код в примере (помещая Where оператор внутри if оператора) гарантирует, что вы получите одинаковые результаты для всех версий SQL Server. Кроме того, платформа .NET Framework реализация Contains метода выполняет сравнение с учетом регистра по умолчанию, но поставщики Entity Framework SQL Server по умолчанию выполняют сравнения без учета регистра. Таким образом, вызов ToUpper метода , чтобы сделать тест явным образом без учета регистра, гарантирует, что результаты не изменятся при последующем изменении кода для использования репозитория, который вернет IEnumerable коллекцию, а не IQueryable объект . (При вызове метода Contains коллекции IEnumerable выполняется реализация .NET Framework; при вызове этого же метода у объекта IQueryable выполняется реализация поставщика базы данных.)

Обработка значений NULL также может отличаться для разных поставщиков баз данных или при использовании IQueryable объекта по сравнению с тем, когда используется IEnumerable коллекция. Например, в некоторых сценариях Where такое условие, как table.Column != 0 может не возвращать столбцы, имеющие null в качестве значения. По умолчанию EF создает дополнительные операторы SQL, чтобы обеспечить равенство значений NULL в базе данных, как и в памяти, но вы можете установить флаг UseDatabaseNullSemantics в EF6 или вызвать метод UseRelationalNulls в EF Core, чтобы настроить это поведение.

Добавление поля поиска в представление индекса учащихся

  1. В Views\Student\Index.cshtml добавьте выделенный код непосредственно перед открывающим table тегом, чтобы создать подпись, текстовое поле и кнопку Поиска.

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    
    @using (Html.BeginForm())
    {
        <p>
            Find by name: @Html.TextBox("SearchString")  
            <input type="submit" value="Search" /></p>
    }
    
    <table>
        <tr>
    
  2. Запустите страницу, введите строку поиска и нажмите кнопку Поиск , чтобы убедиться, что фильтрация работает.

    Обратите внимание, что URL-адрес не содержит строку поиска "an", а это означает, что если вы закладете эту страницу, вы не получите отфильтрованный список при использовании закладки. Это также относится к ссылкам сортировки столбцов, так как они сортируют весь список. Вы измените кнопку Поиск , чтобы использовать строки запроса для условий фильтрации далее в этом руководстве.

Добавление разбиения по страницам

Чтобы добавить подкачки на страницу индекса Students, сначала установите пакет NuGet PagedList.Mvc . Затем вы внесете дополнительные изменения в Index метод и добавите ссылки на страницы в Index представление. PagedList.Mvc является одним из многих хороших пакетов подкачки и сортировки для ASP.NET MVC, и его использование здесь предназначено только в качестве примера, а не в качестве рекомендации по сравнению с другими вариантами.

Установка пакета NuGet PagedList.MVC

Пакет NuGet PagedList.Mvc автоматически устанавливает пакет PagedList в качестве зависимости. Пакет PagedList устанавливает PagedList тип коллекции и методы расширения для IQueryable коллекций и IEnumerable . Методы расширения создают одну страницу данных в PagedList коллекции из или IQueryableIEnumerable, а PagedList коллекция предоставляет несколько свойств и методов, упрощающих разбиение по страницам. Пакет PagedList.Mvc устанавливает вспомогательное средство разбиения по страницам, которое отображает кнопки разбиения по страницам.

  1. В меню Сервис выберите Диспетчер пакетов NuGet , а затем — Консоль диспетчера пакетов.

  2. В окне Консоль диспетчера пакетов убедитесь, что источник пакетаnuget.org , а проект по умолчаниюContosoUniversity, а затем введите следующую команду:

    Install-Package PagedList.Mvc
    
  3. Выполните построение проекта.

Примечание

Пакет PageList больше не поддерживается. Поэтому для текущих проектов лучше использовать пакет X.PagedList . Отличие main заключается в том, что X.PagedList является переносимой сборкой. Это означает, что пакет является кроссплатформенным и может использоваться как для веб-проектов, так и для других проектов .NET. Новый пакет не должен вызывать проблем совместимости, так как он был перенесен в .NET 6 с версии 8.4.

Добавление разбиения на страницы в метод Index

  1. В файле Controllers\StudentController.cs добавьте инструкцию usingPagedList для пространства имен:

    using PagedList;
    
  2. Замените метод Index следующим кодом:

    public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    {
       ViewBag.CurrentSort = sortOrder;
       ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
       ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
    
       if (searchString != null)
       {
          page = 1;
       }
       else
       {
          searchString = currentFilter;
       }
    
       ViewBag.CurrentFilter = searchString;
    
       var students = from s in db.Students
                      select s;
       if (!String.IsNullOrEmpty(searchString))
       {
          students = students.Where(s => s.LastName.Contains(searchString)
                                 || s.FirstMidName.Contains(searchString));
       }
       switch (sortOrder)
       {
          case "name_desc":
             students = students.OrderByDescending(s => s.LastName);
             break;
          case "Date":
             students = students.OrderBy(s => s.EnrollmentDate);
             break;
          case "date_desc":
             students = students.OrderByDescending(s => s.EnrollmentDate);
             break;
          default:  // Name ascending 
             students = students.OrderBy(s => s.LastName);
             break;
       }
    
       int pageSize = 3;
       int pageNumber = (page ?? 1);
       return View(students.ToPagedList(pageNumber, pageSize));
    }
    

    Этот код добавляет page параметр, текущий параметр порядка сортировки и текущий параметр фильтра в сигнатуру метода:

    public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    

    При первом отображении страницы или, если пользователь не щелкнул ссылку для разбиения по страницам или сортировки, все параметры имеют значение NULL. Если щелкнуть ссылку подкачки, page переменная содержит номер отображаемой страницы.

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

    ViewBag.CurrentSort = sortOrder;
    

    Другое свойство , ViewBag.CurrentFilterпредоставляет представление с текущей строкой фильтра. Это значение необходимо включить в ссылки для перелистывания, чтобы при смене страницы сохранить настройки фильтра, кроме того, необходимо восстановить значение фильтра в текстовом поле после обновления страницы. Если строка поиска изменяется во время перелистывания, то номер страницы должен быть сброшен на 1, так как с новым фильтром изменится состав отображаемых данных. Строка поиска изменяется при вводе значения в текстовом поле и нажатии кнопки отправки. В этом случае searchString параметр не имеет значения NULL.

    if (searchString != null)
    {
        page = 1;
    }
    else
    {
        searchString = currentFilter;
    }
    

    В конце метода ToPagedList метод расширения объекта students IQueryable преобразует запрос учащегося в одну страницу учащихся в типе коллекции, поддерживающей разбиение по страницам. Затем одна страница учащихся передается в представление:

    int pageSize = 3;
    int pageNumber = (page ?? 1);
    return View(students.ToPagedList(pageNumber, pageSize));
    

    Метод ToPagedList принимает номер страницы. Два вопросительных знака представляют оператор объединения null. Этот оператор определяет значение по умолчанию для значения null; выражение (page ?? 1) возвращает значение переменной page, если она имеет значение, и возвращает 1, если переменная page имеет значение null.

  1. В файле Views\Student\Index.cshtml замените существующий код следующим кодом. Изменения выделены.

    @model PagedList.IPagedList<ContosoUniversity.Models.Student>
    @using PagedList.Mvc;
    <link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
    
    @{
        ViewBag.Title = "Students";
    }
    
    <h2>Students</h2>
    
    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
            <input type="submit" value="Search" />
        </p>
    }
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th>
                First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
                @Html.ActionLink("Details", "Details", new { id=item.ID }) |
                @Html.ActionLink("Delete", "Delete", new { id=item.ID })
            </td>
        </tr>
    }
    
    </table>
    <br />
    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    
    @Html.PagedListPager(Model, page => Url.Action("Index", 
        new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
    

    Оператор @model в начале страницы указывает на то, что теперь представление принимает объект PagedList, а не объект List.

    Оператор using для PagedList.Mvc предоставляет доступ к вспомогательной функции MVC для кнопок разбиения по страницам.

    Код использует перегрузку BeginForm , которая позволяет указать FormMethod.Get.

    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)  
            <input type="submit" value="Search" />
        </p>
    }
    

    По умолчанию BeginForm отправляет данные формы с помощью POST, что означает, что параметры передаются в тексте сообщения HTTP, а не в URL-адресе в виде строк запроса. При указании метода HTTP GET данные формы передаются в URL-адресе в виде строк запроса, что позволяет добавлять URL-адреса в закладки. В рекомендациях W3C по использованию HTTP GET рекомендуется использовать GET, если действие не приводит к обновлению.

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

    Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
    

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

    @Html.ActionLink("Last Name", "Index", new { sortOrder=ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
    

    Отображается текущая страница и общее количество страниц.

    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    

    Если страницы для отображения отсутствуют, отображается сообщение "Страница 0 из 0". (В этом случае номер страницы больше, чем число страниц, так как Model.PageNumber имеет значение 1, а Model.PageCount — 0.)

    Кнопки разбиения по страницам отображаются вспомогательной PagedListPager службой:

    @Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )
    

    Вспомогающая PagedListPager служба предоставляет ряд параметров, которые можно настроить, включая URL-адреса и стили. Дополнительные сведения см. в разделе TroyGoode / PagedList на сайте GitHub.

  2. Запустите страницу.

    Чтобы убедиться, что постраничный просмотр работает, нажимайте кнопки перелистывания при различном порядке сортировки. Затем введите строку поиска и повторите перелистывание, чтобы убедиться, что разбиение на страницы работает корректно вместе с сортировкой и фильтрацией.

Создание страницы сведений

На странице About веб-сайта "Университет Contoso" будет отображаться количество зачисленных студентов по дням. Для этого понадобится группировка и выполнение простых расчетов в группах. Для выполнения этой задачи нам потребуется следующее:

  • Создать класс модели представления для данных, которые необходимо передать в представление.
  • Измените About метод в контроллере Home .
  • Измените About представление.

Создание модели представления

Создайте папку ViewModels в папке проекта. В этой папке добавьте файл класса EnrollmentDateGroup.cs и замените код шаблона следующим кодом:

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.ViewModels
{
    public class EnrollmentDateGroup
    {
        [DataType(DataType.Date)]
        public DateTime? EnrollmentDate { get; set; }

        public int StudentCount { get; set; }
    }
}

Изменение контроллера Home

  1. В файле HomeController.cs добавьте следующие using инструкции в начало файла:

    using ContosoUniversity.DAL;
    using ContosoUniversity.ViewModels;
    
  2. Добавьте переменную класса для контекста базы данных сразу после открывающей фигурной скобки для класса :

    public class HomeController : Controller
    {
        private SchoolContext db = new SchoolContext();
    
  3. Замените метод About следующим кодом:

    public ActionResult About()
    {
        IQueryable<EnrollmentDateGroup> data = from student in db.Students
                   group student by student.EnrollmentDate into dateGroup
                   select new EnrollmentDateGroup()
                   {
                       EnrollmentDate = dateGroup.Key,
                       StudentCount = dateGroup.Count()
                   };
        return View(data.ToList());
    }
    

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

  4. Dispose Добавьте метод:

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
    

Изменение представления About

  1. Замените код в файле Views\Home\About.cshtml следующим кодом:

    @model IEnumerable<ContosoUniversity.ViewModels.EnrollmentDateGroup>
               
    @{
        ViewBag.Title = "Student Body Statistics";
    }
    
    <h2>Student Body Statistics</h2>
    
    <table>
        <tr>
            <th>
                Enrollment Date
            </th>
            <th>
                Students
            </th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @item.StudentCount
            </td>
        </tr>
    }
    </table>
    
  2. Запустите приложение и щелкните ссылку О программе .

    Количество учащихся для каждой даты регистрации отображается в таблице.

    About_page

Получите код

Скачивание завершенного проекта

Дополнительные ресурсы

Ссылки на другие ресурсы Entity Framework можно найти в разделе ASP.NET Доступ к данным — рекомендуемые ресурсы.

Дальнейшие действия

В этом учебнике рассмотрены следующие задачи.

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

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