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

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

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

Students_Index_page_with_paging

Изучив это руководство, вы:

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

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

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

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

  • В контроллерс\студентконтроллер.КСзамените метод 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, изменяет ее в операторе switch и вызывает метод ToList после инструкции switch. После создания и изменения переменных IQueryable запрос в базу данных не отправляется. Запрос не выполняется до тех пор, пока не будет преобразован объект IQueryable в коллекцию путем вызова метода, такого как ToList. Таким образом, этот код приводит к выполнению одного запроса, который не выполняется до оператора return View.

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

  1. В виевс\студент\индекс.кштмлзамените элементы <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. Чтобы проверить, работает ли сортировка, запустите страницу и щелкните заголовки столбцов Last Name и Date регистрации .

    После того как вы щелкните заголовок Last Name (фамилия ), учащиеся будут отображаться в порядке убывания фамилий.

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

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

  • В контроллерс\студентконтроллер.КСзамените метод 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. Он также добавляет к инструкции LINQ предложение where, которое выбирает только учащихся, чье имя или фамилия содержат строку поиска. Инструкция, которая добавляет предложение Where, выполняется только в том случае, если имеется искомое значение.

Note

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

Например, .NET Framework реализация метода Contains возвращает все строки, когда в нее передается пустая строка, но поставщик 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 в базе данных, подобной работе в памяти, но можно установить флаг уседатабасенуллсемантикс в EF6 или вызвать метод усерелатионалнуллс в EF Core, чтобы настроить это поведение.

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

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

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

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

Установка пакета NuGet Пажедлист. MVC

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

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

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

    Install-Package PagedList.Mvc
    
  3. Создайте проект.

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

  1. В контроллерс\студентконтроллер.КСдобавьте оператор using для пространства имен PagedList.

    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, так как с новым фильтром изменится состав отображаемых данных. Строка поиска изменяется при вводе значения в текстовое поле и нажатии кнопки Submit (отправить). В этом случае параметр 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. В виевс\студент\индекс.кштмлзамените существующий код следующим кодом. Изменения выделены.

    @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 для кнопок разбиения на страницы.

    Код использует перегрузку бегинформ , которая позволяет ему указать форммесод. 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>
    }
    

    Бегинформ по умолчанию отправляет данные формы с помощью записи, что означает, что параметры передаются в тексте сообщения 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-адреса и стили. Дополнительные сведения см. в разделе тройгуде/пажедлист на сайте GitHub.

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

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

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

На странице со сведениями о веб-сайте университета 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. Замените код в файле виевс\хоме\абаут.кштмл следующим кодом:

    @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 (о программе ).

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

    About_page

Получение кода

Скачать завершенный проект

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

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

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

Изучив это руководство, вы:

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

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