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

В предыдущем руководстве вы создали приложение MVC, которое хранит и отображает данные с помощью Entity Framework (EF) 6 и SQL Server LocalDB. В этом руководстве вы изучите и настроите код создания, чтения, обновления и удаления (CRUD), который автоматически создает шаблон MVC в контроллерах и представлениях.

Примечание

Широко распространена практика реализации шаблона репозитория, позволяющего создать уровень абстракции между контроллером и уровнем доступа к данным. Чтобы эти учебники были простыми и сосредоточены на обучении использованию самой EF 6, они не используют репозитории. Сведения о том, как реализовать репозитории, см. в ASP.NET схеме содержимого доступа к данным.

Ниже приведены примеры создаваемых веб-страниц.

Снимок экрана: страница сведений об учащемся.

Снимок экрана: страница создания учащегося.

Снимок экрана: страница удаления учащегося.

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

  • Создание страницы сведений
  • Обновление страницы Create
  • Обновление метода HttpPost Edit
  • Обновление страницы удаления
  • Закрытие подключений к базам данных
  • Обработка транзакций

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

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

Шаблонный код для страницы Students Index не содержит Enrollments свойство , так как это свойство содержит коллекцию. На Details странице вы увидите содержимое коллекции в таблице HTML.

В файле Controllers\StudentController.cs метод действия для Details представления использует метод Find для получения одной Student сущности.

public ActionResult Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

Значение ключа передается методу в качестве id параметра и поступает из данных маршрута в гиперссылке Сведения на странице Индекс.

Совет. Данные маршрута

Данные маршрута — это данные, найденные связывателем модели в сегменте URL-адреса, указанном в таблице маршрутизации. Например, маршрут по умолчанию задает сегменты controller, actionи id :

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

В следующем URL-адресе маршрут по умолчанию сопоставляется Instructor как controller, как action и Index 1 как id; это значения данных маршрута.

http://localhost:1230/Instructor/Index/1?courseID=2021

?courseID=2021 — значение строки запроса. Связыватель модели также будет работать, если передать в id качестве значения строки запроса:

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

URL-адреса создаются операторами ActionLink в представлении Razor. В следующем коде id параметр соответствует маршруту по умолчанию, поэтому id добавляется в данные маршрута.

@Html.ActionLink("Select", "Index", new { id = item.PersonID  })

В следующем коде courseID не соответствует параметру в маршруте по умолчанию, поэтому он добавляется в качестве строки запроса.

@Html.ActionLink("Select", "Index", new { courseID = item.CourseID })

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

  1. Откройте Views\Student\Details.cshtml.

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

    <dt>
        @Html.DisplayNameFor(model => model.LastName)
    </dt>
    <dd>
        @Html.DisplayFor(model => model.LastName)
    </dd>
    
  2. EnrollmentDate После поля и непосредственно перед закрывающим </dl> тегом добавьте выделенный код, чтобы отобразить список регистраций, как показано в следующем примере:

    <dt>
                @Html.DisplayNameFor(model => model.EnrollmentDate)
            </dt>
    
            <dd>
                @Html.DisplayFor(model => model.EnrollmentDate)
            </dd>
            <dt>
                @Html.DisplayNameFor(model => model.Enrollments)
            </dt>
            <dd>
                <table class="table">
                    <tr>
                        <th>Course Title</th>
                        <th>Grade</th>
                    </tr>
                    @foreach (var item in Model.Enrollments)
                    {
                        <tr>
                            <td>
                                @Html.DisplayFor(modelItem => item.Course.Title)
                            </td>
                            <td>
                                @Html.DisplayFor(modelItem => item.Grade)
                            </td>
                        </tr>
                    }
                </table>
            </dd>
        </dl>
    </div>
    <p>
        @Html.ActionLink("Edit", "Edit", new { id = Model.ID }) |
        @Html.ActionLink("Back to List", "Index")
    </p>
    

    Если после вставки кода неправильный отступ, нажмите клавиши CTRL+K, CTRL+D , чтобы отформатировать его.

    Этот код циклически обрабатывает сущности в свойстве навигации Enrollments. Для каждой Enrollment сущности в свойстве отображается название курса и оценка. Название курса извлекается из сущности Course , хранящейся в свойстве Course навигации сущности Enrollments . Все эти данные автоматически извлекаются из базы данных, когда это необходимо. Другими словами, вы используете отложенную загрузку здесь. Вы не указали неотложную загрузкуCourses для свойства навигации, поэтому регистрации не были получены в том же запросе, в который поступили учащиеся. Вместо этого при первой попытке доступа к свойству навигации Enrollments в базу данных отправляется новый запрос для получения данных. Дополнительные сведения о отложенной загрузке и нетеряющейся загрузке см. в руководстве по чтению связанных данных далее в этой серии.

  3. Откройте страницу Сведения, запустив программу (CTRL+F5), выбрав вкладку Учащиеся , а затем щелкните ссылку Сведения для Александра Карсона. (При нажатии клавиши CTRL+F5 , когда файл Details.cshtml открыт, вы получите ошибку HTTP 400. Это связано с тем, что Visual Studio пытается запустить страницу Сведений, но она не была достигнута по ссылке, указывающей учащегося для отображения. В этом случае удалите "Student/Details" из URL-адреса и повторите попытку либо закройте браузер, щелкните проект правой кнопкой мыши и выберите просмотр>в браузере.)

    Вы увидите список курсов и оценок для выбранного учащегося.

  4. Закройте браузер.

Обновление страницы Create

  1. В файле Controllers\StudentController.cs замените HttpPostAttributeCreate метод действия следующим кодом. Этот код добавляет try-catch блок и удаляет ID из атрибута BindAttribute для метода с шаблоном:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "LastName, FirstMidName, EnrollmentDate")]Student student)
    {
        try
        {
            if (ModelState.IsValid)
            {
                db.Students.Add(student);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
        }
        catch (DataException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
        }
        return View(student);
    }
    

    Этот код добавляет сущность, Student созданную связывателем модели ASP.NET MVC, в Students набор сущностей, а затем сохраняет изменения в базе данных. Связыватель модели — это ASP.NET функциональные возможности MVC, которые упрощают работу с данными, отправленными формой; Связыватель модели преобразует опубликованные значения формы в типы CLR и передает их методу действия в параметрах. В этом случае связыватель модели создает Student экземпляр сущности, используя значения свойств из Form коллекции.

    Вы удалили ID из атрибута Bind, так как ID является значением первичного ключа, которое SQL Server будет устанавливаться автоматически при вставке строки. Входные данные от пользователя не задают ID значение.

    Предупреждение системы безопасности. Атрибут ValidateAntiForgeryToken помогает предотвратить атаки с подделкой межсайтовых запросов . Для этого требуется соответствующий Html.AntiForgeryToken() оператор в представлении, который вы увидите позже.

    Атрибут Bind является одним из способов защиты от чрезмерной публикации в сценариях создания. Например, предположим Student , что сущность содержит Secret свойство, которое не требуется задавать на этой веб-странице.

    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
        public string Secret { get; set; }
    
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
    

    Даже если у Secret вас нет поля на веб-странице, злоумышленник может использовать такое средство, как fiddler, или написать код JavaScript для публикации Secret значения формы. Без атрибута, BindAttribute ограничивающего поля, которые связыватель модели использует при создании экземпляраStudent, связыватель модели получает Secret это значение формы и использует его для создания экземпляра сущностиStudent. Таким образом, какое бы значение ни задал злоумышленник для поля Secret, оно будет обновлено в базе данных. На следующем рисунке показано, как средство fiddler добавляет Secret поле (со значением OverPost) к опубликованным значениям формы.

    Снимок экрана: вкладка Composer. В правом верхнем углу элемент Execute обведен красным цветом. В правом нижнем углу элемент Secret равно Over Post обведен красным цветом.

    После этого значение "OverPost" будет успешно добавлено в свойство Secret вставленной строки, хотя вы не разрешали установку этого свойства на веб-странице.

    Для явного перечисления Include полей лучше использовать параметр с атрибутом Bind . Параметр также можно использовать Exclude для блокировки полей, которые вы хотите исключить. Более безопасная причина Include заключается в том, что при добавлении нового свойства в сущность новое поле не защищено автоматически списком Exclude .

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

    Альтернативным способом предотвращения чрезмерной передачи данных, который предпочитают многие разработчики, является использование моделей представлений, а не классов сущностей с привязкой модели. Включайте только те свойства, которые требуется обновлять в модели представления. После завершения привязки модели MVC скопируйте свойства модели представления в экземпляр сущности, при необходимости с помощью такого средства, как AutoMapper. Используйте базу данных. Запись в экземпляре сущности, чтобы задать для нее состояние Без изменений, а затем задать Свойство("PropertyName"). Значение IsModified для каждого свойства сущности, включенного в модель представления, имеет значение true. Этот метод подходит для сценариев редактирования и создания.

    Кроме атрибута Bind , try-catch блок является единственным изменением, внесенным в шаблонный код. Если во время сохранения изменений перехватывается исключение, производное от DataException, отображается сообщение об общей ошибке. Исключения DataException иногда связаны с внешними факторами, а не с ошибкой при программировании приложения, поэтому рекомендуется попробовать повторить выполненные действия снова. В этом примере такое поведение не реализовано, однако в рабочем приложении, как правило, исключения заносятся в журнал. Дополнительные сведения см. в разделе Ведение журналов для анализа статьи Мониторинг и телеметрия (построение реальных облачных приложений для Azure).

    Код в Views\Student\Create.cshtml аналогичен тому, что вы видели в Details.cshtml, за исключением того, что EditorFor вспомогательные функции и ValidationMessageFor используются для каждого поля вместо DisplayFor. Ниже приведен соответствующий код.

    <div class="form-group">
        @Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
    </div>
    

    Файл Create.cshtml также включает @Html.AntiForgeryToken(), который работает с атрибутом ValidateAntiForgeryToken в контроллере для предотвращения атак с подделкой межсайтовых запросов .

    В файле Create.cshtml изменения не требуются.

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

  3. Введите имена и недопустимую дату и нажмите кнопку Создать , чтобы увидеть сообщение об ошибке.

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

    if (ModelState.IsValid)
    {
        db.Students.Add(student);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    
  4. Измените дату на допустимую и щелкните Create (Создать), чтобы добавить нового учащегося на страницу Index (Указатель).

  5. Закройте браузер.

Обновление метода HttpPost Edit

  1. Замените HttpPostAttributeEdit метод действия следующим кодом:

    [HttpPost, ActionName("Edit")]
    [ValidateAntiForgeryToken]
    public ActionResult EditPost(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        var studentToUpdate = db.Students.Find(id);
        if (TryUpdateModel(studentToUpdate, "",
           new string[] { "LastName", "FirstMidName", "EnrollmentDate" }))
        {
            try
            {
                db.SaveChanges();
    
                return RedirectToAction("Index");
            }
            catch (DataException /* dex */)
            {
                //Log the error (uncomment dex variable name and add a line here to write a log.
                ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
            }
        }
        return View(studentToUpdate);
    }
    

    Примечание

    В файле Controllers\StudentController.csHttpGet Edit метод (без атрибутаHttpPost) использует Find метод для получения выбранной Student сущности, как показано в методе Details . Изменять этот метод не нужно.

    Эти изменения реализуют рекомендации по обеспечению безопасности, чтобы предотвратить переотложение. Шаблон создал Bind атрибут и добавил сущность, созданную связывателем модели, в набор сущностей с флагом Modified. Этот код больше не рекомендуется, так как Bind атрибут очищает существующие данные в полях, не перечисленных в параметре Include . В будущем шаблон контроллера MVC будет обновлен таким образом, чтобы он не создавал Bind атрибуты для методов Edit.

    Новый код считывает существующую сущность и вызывает вызовы TryUpdateModel для обновления полей из введенных пользователем данных в опубликованных данных формы. Автоматическое отслеживание изменений Entity Framework устанавливает флаг EntityState.Modified для сущности. При вызове метода SaveChanges флаг приводит к тому, Modified что Entity Framework создает инструкции SQL для обновления строки базы данных. Конфликты параллелизма игнорируются, и обновляются все столбцы строки базы данных, в том числе те, которые пользователь не изменил. (В следующем руководстве показано, как обрабатывать конфликты параллелизма. Если требуется обновить в базе данных только отдельные поля, можно задать для сущности значение EntityState.Unchanged , а для отдельных полей — EntityState.Modified.Modified.)

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

    В результате этих изменений сигнатура метода HttpPost Edit совпадает с сигнатурой метода редактирования HttpGet; поэтому вы переименовали метод EditPost.

    Совет

    Состояния сущностей и методы Attach и SaveChanges

    Контекст базы данных отслеживает состояние синхронизации сущностей в памяти с соответствующими им строками в базе данных. Данные отслеживания определяют, что происходит при вызове метода SaveChanges. Например, при передаче новой сущности в метод Add для этой сущности устанавливается Addedсостояние . Затем при вызове метода SaveChanges контекст базы данных выдает команду SQL INSERT .

    Сущность может находиться в одном из следующих состояний:

    • Added. Сущность еще не существует в базе данных. Метод SaveChanges должен выдавать INSERT оператор .
    • Unchanged. С этой сущностью не нужно выполнять никакие действия с помощью метода SaveChanges. Это начальный статус сущности, который она имеет при чтении из базы данных.
    • Modified. Были изменены значения некоторых или всех свойств сущности. Метод SaveChanges должен выдавать UPDATE оператор .
    • Deleted. Сущность отмечена для удаления. Метод SaveChanges должен выдавать инструкцию DELETE .
    • Detached. Сущность не отслеживается контекстом базы данных.

    В классическом приложении изменения состояния обычно осуществляются автоматически. В классическом приложении вы считываете сущность и вносите изменения в некоторые ее значения свойств. В этом случае состояние сущности автоматически изменится на Modified. Затем при вызове SaveChangesEntity Framework создает инструкцию SQL UPDATE , которая обновляет только фактически измененные свойства.

    Отключенный характер веб-приложений не позволяет использовать эту непрерывную последовательность. DbContext, который считывает сущность, удаляется после отрисовки страницы. При вызове HttpPostEdit метода action выполняется новый запрос, и у вас есть новый экземпляр DbContext, поэтому при вызове SaveChangesentity Framework необходимо вручную задать состояние Modified. сущности в значение Then , entity Framework обновляет все столбцы строки базы данных, так как контекст не может узнать, какие свойства вы изменили.

    Если требуется, чтобы инструкция SQL Update обновляла только те поля, которые фактически были изменены пользователем, можно сохранить исходные значения каким-то образом (например, скрытые поля), чтобы они были доступны при вызове HttpPostEdit метода . Затем можно создать Student сущность с помощью исходных значений, вызвать Attach метод с этой исходной версией сущности, обновить значения сущности до новых значений, а затем вызвать для получения SaveChanges. дополнительных сведений см . статьи Состояния сущностей и СохранениеИзменения и локальные данные.

    Код HTML и Razor в Views\Student\Edit.cshtml аналогичен тому, что вы видели в Файле Create.cshtml, и никаких изменений не требуется.

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

  3. Измените определенные данные и нажмите кнопку Save (Сохранить). Измененные данные отображаются на странице Индекс.

  4. Закройте браузер.

Обновление страницы удаления

В файле Controllers\StudentController.cs код шаблона для метода использует Find метод для HttpGetAttributeDelete получения выбранной Student сущности, как показано в методах Details и Edit . Тем не менее, чтобы реализовать настраиваемое сообщение об ошибке при сбое вызова метода SaveChanges, необходимо добавить некоторые функции в этот метод и соответствующее ему представление.

Как и в случае с операциями обновления и создания, операции удаления требуют двух методов действия. Метод, вызываемый в ответ на запрос GET, отображает представление, позволяющее пользователю утвердить или отменить операцию удаления. Если пользователь подтверждает ее, создается запрос POST. В этом случае вызывается метод , HttpPostDelete а затем этот метод фактически выполняет операцию удаления.

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

  1. Замените HttpGetAttributeDelete метод действия следующим кодом, который управляет отчетами об ошибках:

    public ActionResult Delete(int? id, bool? saveChangesError=false)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        if (saveChangesError.GetValueOrDefault())
        {
            ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
        }
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return HttpNotFound();
        }
        return View(student);
    }
    

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

  2. Замените HttpPostAttributeDelete метод действия (с именем DeleteConfirmed) следующим кодом, который выполняет фактическую операцию удаления и перехватывает все ошибки обновления базы данных.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Delete(int id)
    {
        try
        {
            Student student = db.Students.Find(id);
            db.Students.Remove(student);
            db.SaveChanges();
        }
        catch (DataException/* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
        }
        return RedirectToAction("Index");
    }
    

    Этот код извлекает выбранную сущность, а затем вызывает метод Remove , чтобы задать состояние Deletedсущности в . При вызове метода SaveChanges создается инструкция SQL DELETE. Вы также изменили имя метода действия с DeleteConfirmed на Delete. Шаблонный код с именем HttpPostDelete метода DeleteConfirmed , чтобы дать методу уникальную сигнатуру HttpPost . (Среда CLR требует, чтобы перегруженные методы имели разные параметры метода.) Теперь, когда сигнатуры уникальны, можно придерживаться соглашения MVC и использовать то же имя для HttpPost методов и HttpGet delete.

    Если повышение производительности в большом приложении является приоритетом, можно избежать ненужных SQL-запросов для извлечения строки, заменив строки кода, вызывающие Find методы и Remove , следующим кодом:

    Student studentToDelete = new Student() { ID = id };
    db.Entry(studentToDelete).State = EntityState.Deleted;
    

    Этот код создает экземпляр сущности, Student используя только значение первичного ключа, а затем задает для состояния сущности значение Deleted. Это все, что платформе Entity Framework необходимо для удаления сущности.

    Как уже отмечалось, HttpGetDelete метод не удаляет данные. Выполнение операции удаления в ответ на запрос GET (или, если на то пошло, выполнение любой операции редактирования, операции создания или любой другой операции, которая изменяет данные) создает угрозу безопасности. Дополнительные сведения см . в разделе ASP.NET совет MVC No 46— не используйте удаление ссылок, так как они создают дыры в системе безопасности в блоге Стивена Уолтера.

  3. В views\Student\Delete.cshtml добавьте сообщение об ошибке между h2 заголовком и заголовком h3 , как показано в следующем примере:

    <h2>Delete</h2>
    <p class="error">@ViewBag.ErrorMessage</p>
    <h3>Are you sure you want to delete this?</h3>
    
  4. Запустите страницу, запустив программу, выбрав вкладку Учащиеся и щелкнув гиперссылку Удалить .

  5. Выберите Удалить на странице с надписью Действительно удалить это?.

    Страница Индекс отображается без удаленного учащегося. (Вы увидите пример кода обработки ошибок в действии в учебнике по параллелизму.)

Закрытие подключений к базам данных

Чтобы закрыть подключения к базе данных и как можно скорее освободить ресурсы, которые они удерживают, удалите экземпляр контекста после завершения работы с ним. Именно поэтому шаблонный код предоставляет метод Dispose в конце StudentController класса в Файле StudentController.cs, как показано в следующем примере:

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

Базовый Controller класс уже реализует IDisposable интерфейс, поэтому этот код просто добавляет переопределение Dispose(bool) в метод для явного удаления экземпляра контекста.

Обработка транзакций

По умолчанию платформа Entity Framework реализует транзакции неявно. В сценариях, когда вы вносите изменения в несколько строк или таблиц, а затем вызываете SaveChanges, Entity Framework автоматически гарантирует, что все изменения успешно или все неудачно. Если ошибка происходит после того, как были выполнены некоторые изменения, эти изменения автоматически откатываются. Сценарии, в которых требуется больше контроля( например, если вы хотите включить операции, выполняемые за пределами Entity Framework, в транзакцию), см. в разделе Работа с транзакциями.

Получите код

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

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

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

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

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

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

  • Создание страницы сведений
  • Обновление страницы создания
  • Обновлен метод HttpPost Edit
  • Обновление страницы удаления
  • Закрытие подключений к базам данных
  • Обрабатываемые транзакции

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