Руководство по добавлению сортировки, фильтрации и разбиения на страницы с помощью 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. Это значение по умолчанию, установленное в случае падения в инструкции 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 и вызывает ToList метод после switchswitch оператора. После создания и изменения переменных IQueryable запрос в базу данных не отправляется. Запрос не выполняется, пока объект не преобразуется IQueryable в коллекцию путем вызова такого метода, как ToList. Таким образом, этот код приводит к выполнению одного запроса, который не выполняется до выполнения инструкции return View .

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

  1. В представлениях\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. Запустите страницу и щелкните заголовки столбцов "Фамилия " и "Дата регистрации ", чтобы убедиться, что сортировка работает.

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

Чтобы добавить фильтрацию на страницу индекса учащихся, необходимо добавить текстовое поле и кнопку отправки в представление и внести соответствующие изменения в 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 . Например, в некоторых сценариях условие, напримерtable.Column != 0, может не возвращать столбцыWhere, имеющие null значение. По умолчанию EF создает дополнительные операторы SQL для обеспечения равенства между значениями NULL в базе данных, как она работает в памяти, но для настройки этого поведения можно задать флаг UseDatabaseNullSemantics в EF6 или вызвать метод UseRelationalNulls в EF Core.

Добавление поля поиска в представление индекса student

  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", что означает, что при закладке этой страницы вы не получите отфильтрованный список при использовании закладки. Это также относится к ссылкам сортировки столбцов, так как они будут сортировать весь список. Вы измените кнопку "Поиск ", чтобы использовать строки запроса для условий фильтрации далее в этом руководстве.

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

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

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

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

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

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

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

[!NOTE]

Пакет PageList больше не поддерживается. Поэтому для текущих проектов лучше использовать пакет X.PagedList . Основное отличие заключается в том, что 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. (В этом случае номер страницы больше числа страниц, так как 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 Доступ к данным — рекомендуемые ресурсы.

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

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

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

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