Руководство. Обновление связанных данных — ASP.NET MVC с помощью EF Core
В предыдущем руководстве вы отобразили связанные данные. В этом руководстве описано обновление связанных данных путем обновления полей внешнего ключа и свойств навигации.
На следующих рисунках изображены некоторые из страниц, с которыми вы будете работать.
Изучив это руководство, вы:
- Настройка страниц курсов
- Добавление страницы редактирования данных о преподавателях
- Добавление курсов на страницу редактирования
- Обновление страницы удаления
- Добавление расположения кабинета и курсов на страницу создания
Необходимые компоненты
Настройка страниц курсов
Создаваемая сущность Course
должна иметь связь с существующей кафедрой. Чтобы упростить эту задачу, шаблонный код включает методы контроллеров, а также представления "Create" (Создание) и "Edit" (Редактирование) с раскрывающимся списком для выбора кафедры. Раскрывающийся список задает свойство внешнего ключа Course.DepartmentID
, и это все, что нужно Entity Framework для загрузки свойства навигации Department
с соответствующей сущностью Department
. Вы будете использовать этот шаблонный код, немного его изменив, чтобы добавить обработку ошибок и сортировку раскрывающегося списка.
Удалите CoursesController.cs
четыре метода создания и редактирования и замените их следующим кодом:
public IActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("CourseID,Credits,DepartmentID,Title")] Course course)
{
if (ModelState.IsValid)
{
_context.Add(course);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var courseToUpdate = await _context.Courses
.FirstOrDefaultAsync(c => c.CourseID == id);
if (await TryUpdateModelAsync<Course>(courseToUpdate,
"",
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}
После метода HttpPost Edit
создайте метод, загружающий сведения о кафедре для раскрывающегося списка.
private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
var departmentsQuery = from d in _context.Departments
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(departmentsQuery.AsNoTracking(), "DepartmentID", "Name", selectedDepartment);
}
Метод PopulateDepartmentsDropDownList
возвращает список всех кафедр, отсортированных по имени, создает коллекцию SelectList
для раскрывающегося списка и передает ее в представление в ViewBag
. Этот метод принимает необязательный параметр selectedDepartment
, позволяющий вызывающему коду указать элемент, который будет выбран при отрисовке раскрывающегося списка. Представление передаст имя "DepartmentID" во вспомогательную функцию тегов <select>
, после чего ей станет известно, что нужно искать в объекте ViewBag
коллекцию SelectList
с именем "DepartmentID".
Метод HttpGet Create
вызывает метод PopulateDepartmentsDropDownList
без установки выбранного элемента, так как кафедра для нового курса еще не задана:
public IActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
Метод HttpGet Edit
задает выбранный элемент на основе идентификатора кафедры, который уже назначен редактируемому курсу:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
Методы HttpPost для Create
и Edit
также содержат код, который задает выбранный элемент, когда они повторно отображают страницу после ошибки. Это гарантирует, что при повторном отображении страницы для вывода сообщения об ошибке сохраняется выбор кафедры.
Добавление .AsNoTrackin в методы Details и Delete
Чтобы оптимизировать производительность страниц "Details" (Сведения) и "Delete" (Удаление) курса, добавьте вызовы AsNoTracking
в методы Details
и HttpGet Delete
.
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
return View(course);
}
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
return View(course);
}
Изменение представлений курса
Добавьте Views/Courses/Create.cshtml
в раскрывающийся список "Выбор отдела", измените подпись с DepartmentID на Отдел и добавьте сообщение проверки.
<div class="form-group">
<label asp-for="Department" class="control-label"></label>
<select asp-for="DepartmentID" class="form-control" asp-items="ViewBag.DepartmentID">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="DepartmentID" class="text-danger" />
</div>
Внесите Views/Courses/Edit.cshtml
то же самое изменение в поле "Отдел", в которое вы только что сделали Create.cshtml
.
Views/Courses/Edit.cshtml
Кроме того, добавьте поле номера курса перед полем "Заголовок". Так как номер курса является первичным ключом, он отображается, но не может быть изменен.
<div class="form-group">
<label asp-for="CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.CourseID)</div>
</div>
Представление "Edit" (Редактирование) уже содержит скрытое поле (<input type="hidden">
) для номера курса. Добавление вспомогательной функции тегов <label>
не устраняет потребность в этом скрытом поле, так как не приводит к включению номера курса в передаваемые данные, когда пользователь нажимает кнопку Save (Сохранить) на странице Edit (Редактирование).
Добавьте Views/Courses/Delete.cshtml
поле номера курса в верхней части и измените идентификатор отдела на имя отдела.
@model ContosoUniversity.Models.Course
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.Name)
</dd>
</dl>
<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>
В Views/Courses/Details.cshtml
, сделайте то же изменение, что вы только что сделали Delete.cshtml
.
Тестирование страниц курса
Запустите приложение, выберите вкладку Courses (Курсы), щелкните Create New (Создать) и введите данные для нового курса:
Нажмите кнопку Создать. Отображается страница индекса курсов, где в список добавлен новый курс. Название кафедры в списке страницы индекса поступает из свойства навигации, показывая, что связь установлена правильно.
Нажмите кнопку Edit (Изменить) на странице индекса курсов.
Измените данные на странице и нажмите кнопку Save (Сохранить). Отображается страница индекса курсов с обновленными данными о курсах.
Добавление страницы редактирования данных о преподавателях
При редактировании записи преподавателя может потребоваться обновить назначенный преподавателю кабинет. Сущность Instructor
имеет связь "один к нулю или к одному" с сущностью OfficeAssignment
, что означает, что код должен обрабатывать следующие ситуации.
Если пользователь сбрасывает назначение кабинета, которое изначально имело некоторое значение, удалите сущность
OfficeAssignment
.Если пользователь вводит значение для назначения кабинета, которое изначально было пустым, создайте сущность
OfficeAssignment
.Если пользователь изменяет значение для назначения кабинета, измените значение в существующей сущности
OfficeAssignment
.
Обновление контроллера преподавателей
В InstructorsController.cs
измените код метода HttpGet Edit
, чтобы он загружал свойство навигации OfficeAssignment
сущности Instructor и вызывал AsNoTracking
:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var instructor = await _context.Instructors
.Include(i => i.OfficeAssignment)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
return View(instructor);
}
Замените метод HttpPost Edit
следующим кодом, чтобы обрабатывать обновления назначения кабинета:
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var instructorToUpdate = await _context.Instructors
.Include(i => i.OfficeAssignment)
.FirstOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
return View(instructorToUpdate);
}
Код делает следующее:
Изменяет имя метода на
EditPost
, так как сигнатура теперь аналогична методу HttpGetEdit
(атрибутActionName
указывает, что URL-адрес/Edit/
по-прежнему используется).Получает текущую сущность
Instructor
из базы данных, используя безотложную загрузку для свойства навигацииOfficeAssignment
. Это аналогично тому, что вы сделали в методе HttpGetEdit
.Обновляет извлеченную сущность
Instructor
, используя значения из связывателя модели. ПерегрузкаTryUpdateModel
позволяет объявить включаемые свойства. Это защищает от чрезмерной передачи данных, как описано во втором руководстве.if (await TryUpdateModelAsync<Instructor>( instructorToUpdate, "", i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
Если расположение кабинета отсутствует, задает для свойства
Instructor.OfficeAssignment
значение NULL, что приводит к удалению связанной строки в таблицеOfficeAssignment
.if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location)) { instructorToUpdate.OfficeAssignment = null; }
Сохраняет изменения в базу данных.
Обновление представления редактирования преподавателя
Добавьте Views/Instructors/Edit.cshtml
новое поле для редактирования расположения office в конце перед кнопкой "Сохранить ":
<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label"></label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>
Запустите приложение, выберите вкладку Instructors (Преподаватели), а затем щелкните Edit (Изменить) для преподавателя. Измените значение Office Location (Расположение кабинета) и нажмите кнопку Save (Сохранить).
Добавление курсов на страницу редактирования
Преподаватели могут вести любое число курсов. Теперь вы усовершенствуете страницу редактирования преподавателя, добавив возможность изменять назначения курсов с помощью группы флажков, как показано на следующем снимке экрана.
Между сущностями Course
и Instructor
действует связь "многие ко многим". Для добавления и удаления связей можно добавлять сущности в список объединенного набора сущностей CourseAssignments
и удалять их из него.
Элементы пользовательского интерфейса, позволяющие изменять назначенные преподавателю курсы, представляют собой группу флажков. Отображается флажок для каждого курса в базе данных, и флажки установлены для тех курсов, которые назначены текущему преподавателю. Пользователь может устанавливать и снимать флажки, изменяя назначения курсов. Если бы количество курсов было значительно больше, возможно, вам потребовалось бы использовать другой метод отображения данных в этом представлении, но вы бы использовали тот же самый способ управления сущностью объединения для создания и удаления связей.
Обновление контроллера преподавателей
Чтобы указать в представлении данные для списка флажков, используйте класс моделей представления.
Создайте AssignedCourseData.cs
в папке SchoolViewModels и замените существующий код следующим кодом:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
Замените InstructorsController.cs
метод HttpGet Edit
следующим кодом. Изменения выделены.
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var instructor = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
private void PopulateAssignedCourseData(Instructor instructor)
{
var allCourses = _context.Courses;
var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
var viewModel = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
viewModel.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
ViewData["Courses"] = viewModel;
}
Этот код добавляет безотложную загрузку для свойства навигации Courses
и вызывает новый метод PopulateAssignedCourseData
, который предоставляет сведения для массива флажков, используя класс моделей представления AssignedCourseData
.
Код в методе PopulateAssignedCourseData
считывает все сущности Course
, чтобы загрузить список курсов, используя класс модели представления. Для каждого курса код проверяет, существует ли этот курс в свойстве навигации Courses
преподавателя. Чтобы создать эффективную подстановку при проверке того, назначен ли курс преподавателю, назначаемые курсы помещаются в коллекцию HashSet
. У курсов, назначенных преподавателю, для свойства Assigned
задается значение true. Представление будет использовать это свойство, чтобы определить, какие флажки нужно отображать как выбранные. Наконец, список передается в представление в ViewData
.
Добавьте код, выполняемый, когда пользователь нажимает кнопку Save (Сохранить). Замените метод EditPost
на следующий код и добавьте новый метод, который обновляет свойство навигации Courses
для сущности Instructor.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
{
if (id == null)
{
return NotFound();
}
var instructorToUpdate = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.FirstOrDefaultAsync(m => m.ID == id);
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
Сигнатура метода теперь отличается от метода HttpGet Edit
, поэтому имя метода изменяется с EditPost
обратно на Edit
.
Так как представление не содержит коллекцию сущностей Course, связыватель модели не может автоматически обновить свойство навигации CourseAssignments
. Вместо использования связывателя модели для обновления свойства навигации CourseAssignments
вы делаете это в новом методе UpdateInstructorCourses
. Поэтому нужно исключить свойство CourseAssignments
из привязки модели. Это не требует внесения никаких изменений в код, вызывающем TryUpdateModel
, так как вы используете перегрузку, требующую явного утверждения, а CourseAssignments
отсутствует в списке включений.
Если флажки не выбраны, код в UpdateInstructorCourses
инициализирует свойство навигации CourseAssignments
с использованием пустой коллекции и возвращает следующее:
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
После этого код в цикле проходит по всем курсам в базе данных и сравнивает каждый из них с теми, которые сейчас назначены преподавателю, в противоположность тем, которые были выбраны в представлении. Чтобы упростить эффективную подстановку, последние две коллекции хранятся в объектах HashSet
.
Если флажок для курса установлен, но курс отсутствует в свойстве навигации Instructor.CourseAssignments
, этот курс добавляется в коллекцию в свойстве навигации.
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
Если флажок для курса не установлен, но курс присутствует в свойстве навигации Instructor.CourseAssignments
, этот курс удаляется из свойства навигации.
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
Обновление представлений преподавателя
В файле Views/Instructors/Edit.cshtml
добавьте поле Courses (Курсы) с массивом флажков, добавив приведенный ниже код сразу после элементов div
для поля Office (Кабинет) и перед элементом div
для кнопки Save (Сохранить).
Примечание.
При вставке кода в Visual Studio разрывы строк могут поменяться, нарушая код. Если код выглядит иначе после вставки, нажмите клавиши CTRL + Z один раз для отмены автоматического форматирования. Это исправляет разрывы строк, благодаря чему код приобретает показанный здесь вид. Выравнивать отступы необязательно, однако строки @:</tr><tr>
, @:<td>
, @:</td>
и @:</tr>
должны находиться на одной строке, как показано здесь. В противном случае возникает ошибка времени выполнения. Выделите блок нового кода и три раза нажмите клавишу TAB, чтобы выровнять его с существующим кодом. Эта проблема исправлена в Visual Studio 2019.
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses = ViewBag.Courses;
foreach (var course in courses)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
Этот код создает таблицу HTML с тремя столбцами. Каждый столбец содержит флажок, за которым следует подпись с номером и названием курса. Все флажки имеют одно имя (selectedCourses), уведомляющее связыватель модели, что их следует рассматривать как группу. Атрибуту значения для каждого флажка присваивается значение CourseID
. При публикации страницы связыватель модели передает контроллеру массив, содержащий значения CourseID
только для выбранных флажков.
Флажкам, назначенным преподавателю, заданы атрибуты checked (установлены), поэтому при первичной отрисовке флажков курсов они отображаются установленными.
Запустите приложение, выберите вкладку Instructors (Преподаватели), а затем щелкните Edit (Изменить) для преподавателя, чтобы открыть страницу Edit (Редактирование).
Измените некоторые назначения курсов и нажмите кнопку "Save" (Сохранить). Вносимые вами изменения отражаются на странице индекса.
Примечание.
Описываемый здесь подход к редактированию данных курсов для преподавателя эффективен при ограниченном числе курсов. Для коллекций большего размера следовало бы применять другой пользовательский интерфейс и другой метод обновления.
Обновление страницы удаления
Удалите InstructorsController.cs
DeleteConfirmed
метод и вставьте следующий код в его место.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
Instructor instructor = await _context.Instructors
.Include(i => i.CourseAssignments)
.SingleAsync(i => i.ID == id);
var departments = await _context.Departments
.Where(d => d.InstructorID == id)
.ToListAsync();
departments.ForEach(d => d.InstructorID = null);
_context.Instructors.Remove(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
Этот код вносит следующие изменения.
Выполняет безотложную загрузку для свойства навигации
CourseAssignments
. Вам нужно включить его, иначе EF не будет знать о связанных сущностяхCourseAssignment
и не удалит их. Чтобы избежать необходимости считывать их, можно настроить каскадное удаление в базе данных.Если преподаватель, которого требуется удалить, назначен в качестве администратора любой из кафедр, удаляется назначение преподавателя из таких кафедр.
Добавление расположения кабинета и курсов на страницу создания
В файле InstructorsController.cs
удалите методы HttpGet и HttpPost Create
и вставьте вместо них следующий код:
public IActionResult Create()
{
var instructor = new Instructor();
instructor.CourseAssignments = new List<CourseAssignment>();
PopulateAssignedCourseData(instructor);
return View();
}
// POST: Instructors/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
{
if (selectedCourses != null)
{
instructor.CourseAssignments = new List<CourseAssignment>();
foreach (var course in selectedCourses)
{
var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
instructor.CourseAssignments.Add(courseToAdd);
}
}
if (ModelState.IsValid)
{
_context.Add(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
Этот код аналогичен коду для методов Edit
, за исключением того, что изначально никакие курсы не выбраны. Метод HttpGet Create
вызывает метод PopulateAssignedCourseData
не потому, что могут быть выбраны курсы, а чтобы предоставить пустую коллекцию для цикла foreach
в представлении (в противном случае код представления выдаст исключение пустой ссылки).
Метод HttpPost Create
добавляет каждый выбранный курс в свойство навигации CourseAssignments
до того, как выполнить поиск ошибок проверки и добавить нового преподавателя в базу данных. Курсы добавляются даже при наличии ошибок модели, поэтому когда имеются такие ошибки (например, пользователь ввел недопустимую дату) и страница отображается повторно с сообщением об ошибке, все выбранные курсы восстанавливаются автоматически.
Обратите внимание, что для добавления курсов в свойство навигации CourseAssignments
нужно инициализировать это свойство как пустую коллекцию:
instructor.CourseAssignments = new List<CourseAssignment>();
Это можно сделать не только в коде контроллера, но и в модели Instructor
, изменив метод получения свойств для автоматического создания коллекции, если она не существует, как показано в следующем примере:
private ICollection<CourseAssignment> _courseAssignments;
public ICollection<CourseAssignment> CourseAssignments
{
get
{
return _courseAssignments ?? (_courseAssignments = new List<CourseAssignment>());
}
set
{
_courseAssignments = value;
}
}
При подобном изменении свойства CourseAssignments
можно удалить код явной инициализации свойства в контроллере.
Добавьте Views/Instructor/Create.cshtml
текстовое поле расположения office и проверка boxes для курсов перед кнопкой "Отправить". Как и в случае со страницей редактирования, исправьте форматирование, если Visual Studio переформатирует код при вставке.
<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label"></label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses = ViewBag.Courses;
foreach (var course in courses)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
Проверьте работу, запустив приложение и создав преподавателя.
Обработка транзакций
Как описано в руководстве по CRUD, платформа Entity Framework реализует транзакции неявно. Если вам требуется дополнительный контроль, например в сценариях с операциями, выполняемыми в транзакции вне платформы Entity Framework, ознакомьтесь с разделом Транзакции.
Получение кода
Скачайте или ознакомьтесь с готовым приложением.
Следующие шаги
Изучив это руководство, вы:
- Настройка страниц курсов
- Добавление страницы редактирования преподавателей
- Добавление курсов на страницу редактирования
- Обновление страницы удаления
- Добавление расположения кабинета и курсов на страницу создания
В следующем учебнике описано, как обрабатывать конфликты параллелизма.
ASP.NET Core