Руководство. Обновление связанных данных с помощью EF в приложении MVC ASP.NET
В предыдущем руководстве вы отображали связанные данные. В этом руководстве вы обновите связанные данные. Для большинства связей это можно сделать, обновив поля внешнего ключа или свойства навигации. Для связей "многие ко многим" платформа Entity Framework не предоставляет таблицу соединения напрямую, поэтому вы добавляете и удаляете сущности в соответствующие свойства навигации и из него.
На следующих рисунках изображены некоторые из страниц, с которыми вы будете работать.
Изучив это руководство, вы:
- Настройка страниц курсов
- Добавление office на страницу инструкторов
- Добавление курсов на страницу преподавателей
- Обновление DeleteConfirmed
- Добавление расположения кабинета и курсов на страницу создания
Необходимые компоненты
Настройка страниц курсов
Создаваемая сущность курса должна иметь связь с существующей кафедрой. Чтобы упростить эту задачу, шаблонный код включает методы контроллеров, а также представления "Create" (Создание) и "Edit" (Редактирование) с раскрывающимся списком для выбора кафедры. Раскрывающийся список задает свойство внешнего ключа Course.DepartmentID
, и это все, что нужно Entity Framework для загрузки свойства навигации Department
с соответствующей сущностью Department
. Вы будете использовать этот шаблонный код, немного его изменив, чтобы добавить обработку ошибок и сортировку раскрывающегося списка.
В CourseController.cs удалите четыре Create
и методы и Edit
замените их следующим кодом:
public ActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID")]Course course)
{
try
{
if (ModelState.IsValid)
{
db.Courses.Add(course);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (RetryLimitExceededException /* 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.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Course course = db.Courses.Find(id);
if (course == null)
{
return HttpNotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var courseToUpdate = db.Courses.Find(id);
if (TryUpdateModel(courseToUpdate, "",
new string[] { "Title", "Credits", "DepartmentID" }))
{
try
{
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* 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.");
}
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}
private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
var departmentsQuery = from d in db.Departments
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
}
Добавьте следующую using
инструкцию в начале файла:
using System.Data.Entity.Infrastructure;
Метод PopulateDepartmentsDropDownList
получает список всех отделов, отсортированных по имени, создает SelectList
коллекцию для раскрывающегося списка и передает коллекцию в представление в свойстве ViewBag
. Этот метод принимает необязательный параметр selectedDepartment
, позволяющий вызывающему коду указать элемент, который будет выбран при отрисовке раскрывающегося списка. Представление передает имя DepartmentID
вспомогателя DropDownList, а помощник затем знает, как выглядеть в объекте ViewBag
для именованногоDepartmentID
SelectList
.
Метод HttpGet
Create
вызывает PopulateDepartmentsDropDownList
метод без настройки выбранного элемента, так как для нового курса отдел еще не установлен:
public ActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
Метод HttpGet
Edit
задает выбранный элемент на основе идентификатора отдела, который уже назначен курсу для редактирования:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Course course = db.Courses.Find(id);
if (course == null)
{
return HttpNotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
Методы HttpPost
для обоих Create
элементов, Edit
а также код, который задает выбранный элемент при повторном воспроизведении страницы после ошибки:
catch (RetryLimitExceededException /* 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.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
Этот код гарантирует, что при повторном воспроизведении страницы для отображения сообщения об ошибке любой отдел остается выбранным.
Представления курсов уже сформированы с раскрывающимся списком для поля отдела, но вы не хотите, чтобы заголовок DepartmentID для этого поля был выделен, поэтому внесите следующее выделенное изменение в файл Views\Course\Create.cshtml , чтобы изменить заголовок.
@model ContosoUniversity.Models.Course
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Course</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.CourseID)
@Html.ValidationMessageFor(model => model.CourseID)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Credits, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Credits)
@Html.ValidationMessageFor(model => model.Credits)
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="DepartmentID">Department</label>
<div class="col-md-10">
@Html.DropDownList("DepartmentID", String.Empty)
@Html.ValidationMessageFor(model => model.DepartmentID)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Внесите те же изменения в Views\Course\Edit.cshtml.
Обычно шаблон не создает первичный ключ, так как значение ключа создается базой данных и не может быть изменено и не является значимым значением для отображения пользователям. Для сущностей Course шаблон включает текстовое поле для CourseID
поля, так как он понимает, что атрибут означает, что DatabaseGeneratedOption.None
пользователь должен иметь возможность ввести значение первичного ключа. Но он не понимает, что, потому что число имеет смысл, которое вы хотите увидеть в других представлениях, поэтому вам нужно добавить его вручную.
В Views\Course\Edit.cshtml добавьте поле номера курса перед полем "Заголовок ". Так как это первичный ключ, он отображается, но его нельзя изменить.
<div class="form-group">
@Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DisplayFor(model => model.CourseID)
</div>
</div>
В представлении "Изменить" уже есть скрытое поле (Html.HiddenFor
вспомогательный) для номера курса. Добавление вспомогательной функции Html.LabelFor не устраняет необходимость скрытого поля, так как оно не приводит к включению номера курса в опубликованные данные, когда пользователь нажимает кнопку "Сохранить" на странице "Изменить ".
В Views\Course\Delete.cshtml и Views\Course\Details.cshtml измените заголовок имени отдела на "Имя" на "Отдел" и добавьте поле номера курса перед полем "Название ".
<dt>
Department
</dt>
<dd>
@Html.DisplayFor(model => model.Department.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.CourseID)
</dt>
<dd>
@Html.DisplayFor(model => model.CourseID)
</dd>
Запустите страницу "Создать" (отобразите страницу индекса курса и нажмите кнопку "Создать") и введите данные для нового курса:
Значение | Параметр |
---|---|
Число | Введите 1000. |
Заголовок | Введите Algebra. |
Благодарности | Введите 4. |
Отдел | Выберите математику. |
Нажмите кнопку Создать. Страница индекса курса отображается с новым курсом, добавленным в список. Название кафедры в списке страницы индекса поступает из свойства навигации, показывая, что связь установлена правильно.
Запустите страницу "Изменить" (отобразите страницу "Индекс курса" и нажмите кнопку "Изменить" для курса).
Измените данные на странице и нажмите кнопку Save (Сохранить). Страница индекса курса отображается с обновленными данными курса.
Добавление office на страницу инструкторов
При редактировании записи преподавателя может потребоваться обновить назначенный преподавателю кабинет. Сущность Instructor
имеет связь "один к нулю" или "один" с сущностью OfficeAssignment
, что означает, что необходимо обрабатывать следующие ситуации:
- Если пользователь очищает назначение office и изначально имеет значение, необходимо удалить и удалить
OfficeAssignment
сущность. - Если пользователь вводит значение назначения office и изначально был пустым, необходимо создать новую
OfficeAssignment
сущность. - Если пользователь изменяет значение назначения office, необходимо изменить значение существующей
OfficeAssignment
сущности.
Откройте InstructorController.cs и просмотрите HttpGet
Edit
метод:
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Instructor instructor = db.Instructors.Find(id);
if (instructor == null)
{
return HttpNotFound();
}
ViewBag.ID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.ID);
return View(instructor);
}
Шаблонный код здесь не является нужным. Он настраивает данные для раскрывающегося списка, но вам нужно текстовое поле. Замените этот метод следующим кодом:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
if (instructor == null)
{
return HttpNotFound();
}
return View(instructor);
}
Этот код удаляет инструкцию ViewBag
и добавляет страстную загрузку связанной OfficeAssignment
сущности. Вы не можете выполнять страстную загрузку с Find
помощью метода, поэтому Single
Where
вместо этого используются методы для выбора инструктора.
Замените HttpPost
Edit
метод следующим кодом. который обрабатывает обновления назначения office:
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
if (TryUpdateModel(instructorToUpdate, "",
new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
{
try
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* 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(instructorToUpdate);
}
Ссылка на RetryLimitExceededException
требуемую using
инструкцию; чтобы добавить ее, наведите указатель мыши RetryLimitExceededException
на нее. Появится следующее сообщение:
Выберите " Показать потенциальные исправления", а затем с помощью System.Data.Entity.Infrastructure
Код делает следующее:
Изменяет имя
EditPost
метода, так как подпись теперьHttpGet
совпадает с методом (ActionName
атрибут указывает, что url-адрес /Edit/ по-прежнему используется).Получает текущую сущность
Instructor
из базы данных, используя безотложную загрузку для свойства навигацииOfficeAssignment
. Это то же самое, что и в методеHttpGet
Edit
.Обновляет извлеченную сущность
Instructor
, используя значения из связывателя модели. Перегрузка TryUpdateModel позволяет перечислить свойства, которые необходимо включить. Это защищает от чрезмерной передачи данных, как описано во втором руководстве.if (TryUpdateModel(instructorToUpdate, "", new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
Если расположение кабинета отсутствует, задает для свойства
Instructor.OfficeAssignment
значение NULL, что приводит к удалению связанной строки в таблицеOfficeAssignment
.if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location)) { instructorToUpdate.OfficeAssignment = null; }
Сохраняет изменения в базу данных.
В Views\Instructor\Edit.cshtml после div
элементов поля "Дата найма" добавьте новое поле для редактирования расположения офиса:
<div class="form-group">
@Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.OfficeAssignment.Location)
@Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
</div>
</div>
Запустите страницу (перейдите на вкладку "Инструкторы" и нажмите кнопку "Изменить " для инструктора). Измените значение Office Location (Расположение кабинета) и нажмите кнопку Save (Сохранить).
Добавление курсов на страницу преподавателей
Преподаватели могут вести любое число курсов. Теперь вы улучшите страницу редактирования инструктора, добавив возможность изменить задания курса с помощью группы флажков.
Связь между Course
сущностями — Instructor
"многие ко многим", что означает, что у вас нет прямого доступа к свойствам внешнего ключа, которые находятся в таблице соединения. Вместо этого вы добавляете и удаляете сущности в свойство навигации и из него Instructor.Courses
.
Пользовательский интерфейс, позволяющий изменить назначенные для преподавателя курсы, представляет собой группу флажков. Отображается флажок для каждого курса в базе данных, а флажки для курсов, назначенных данному преподавателю, установлены. Пользователь может устанавливать и снимать флажки, изменяя назначения курсов. Если число курсов было гораздо больше, вы, вероятно, хотите использовать другой метод представления данных в представлении, но вы будете использовать тот же метод управления свойствами навигации для создания или удаления связей.
Чтобы предоставить данные в представлении для списка флажков, нужно использовать класс модели представления. Создайте AssignedCourseData.cs в папке ViewModels и замените существующий код следующим кодом:
namespace ContosoUniversity.ViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
В InstructorController.cs замените HttpGet
Edit
метод следующим кодом. Изменения выделены.
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.ID == id)
.Single();
if (instructor == null)
{
return HttpNotFound();
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
private void PopulateAssignedCourseData(Instructor instructor)
{
var allCourses = db.Courses;
var instructorCourses = new HashSet<int>(instructor.Courses.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)
});
}
ViewBag.Courses = viewModel;
}
Код добавляет безотложную загрузку для свойства навигации Courses
и вызывает новый метод PopulateAssignedCourseData
для предоставления сведений массиву флажков с помощью класса модели представления AssignedCourseData
.
Код в методе PopulateAssignedCourseData
считывает все сущности Course
, чтобы загрузить список курсов, используя класс модели представления. Для каждого курса код проверяет, существует ли этот курс в свойстве навигации Courses
преподавателя. Чтобы создать эффективный поиск при проверке того, назначен ли курс инструктору, курсы, назначенные инструктору, помещаются в коллекцию HashSet . Свойство Assigned
имеет значение true
для курсов, которым назначен инструктор. Представление будет использовать это свойство, чтобы определить, какие флажки нужно отображать как выбранные. Наконец, список передается в представление в свойстве ViewBag
.
Добавьте код, выполняемый, когда пользователь нажимает кнопку Save (Сохранить). Замените EditPost
метод следующим кодом, который вызывает новый метод, который обновляет Courses
свойство навигации сущности Instructor
. Изменения выделены.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int? id, string[] selectedCourses)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.ID == id)
.Single();
if (TryUpdateModel(instructorToUpdate, "",
new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
{
try
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* 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.");
}
}
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.Courses.Select(c => c.CourseID));
foreach (var course in db.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Remove(course);
}
}
}
}
Сигнатура метода теперь отличается от HttpGet
Edit
метода, поэтому имя метода изменяется с EditPost
обратно на Edit
.
Так как представление не содержит коллекцию сущностей Course
, привязка модели не может автоматически обновлять Courses
свойство навигации. Вместо использования привязки модели для обновления Courses
свойства навигации вы сделаете это в новом UpdateInstructorCourses
методе. Поэтому нужно исключить свойство Courses
из привязки модели. Для этого не требуется никаких изменений в коде, который вызывает TryUpdateModel , так как вы используете явную перегрузку списка и Courses
не входит в список включения.
Если флажки не были выбраны, код в UpdateInstructorCourses
инициализирует Courses
свойство навигации с пустой коллекцией:
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
После этого код в цикле проходит по всем курсам в базе данных и сравнивает каждый из них с теми, которые сейчас назначены преподавателю, в противоположность тем, которые были выбраны в представлении. Чтобы упростить эффективную подстановку, последние две коллекции хранятся в объектах HashSet
.
Если флажок для курса был установлен, но курс отсутствует в свойстве навигации Instructor.Courses
, этот курс добавляется в коллекцию в свойстве навигации.
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
Если флажок для курса не был установлен, но курс присутствует в свойстве навигации Instructor.Courses
, этот курс удаляется из свойства навигации.
else
{
if (instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Remove(course);
}
}
В Views\Instructor\Edit.cshtml добавьте поле Courses с массивом флажков, добавив следующий код сразу после div
элементов OfficeAssignment
поля и перед элементом div
для кнопки "Сохранить ":
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.ViewModels.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>
После вставки кода, если разрывы строк и отступы не выглядят здесь, вручную исправьте все, что вы видите здесь. Выравнивать отступы необязательно, однако строки @</tr><tr>
, @:<td>
, @:</td>
и @</tr>
должны находиться на одной строке, как показано здесь. В противном случае возникает ошибка времени выполнения.
Этот код создает таблицу HTML с тремя столбцами. Каждый столбец содержит флажок, за которым идет заголовок с номером и названием курса. Все флажки имеют то же имя ("selectedCourses"), которое сообщает привязщику модели, что они должны рассматриваться как группа. Атрибут value
каждого флажка имеет значение CourseID.
"Когда страница размещена", привязка модели передает массив контроллеру, состоящему из CourseID
значений только выбранных флажков.
Когда флажки изначально отображаются, те, которые предназначены для курсов, назначенных инструктору, имеют checked
атрибуты, которые выбирают их (отображает их).
После изменения заданий курса вы захотите проверить изменения, когда сайт возвращается на страницу Index
. Поэтому необходимо добавить столбец в таблицу на этой странице. В этом случае не нужно использовать ViewBag
объект, так как сведения, которые вы хотите отобразить, уже находится в Courses
свойстве Instructor
навигации сущности, которую вы передаете на страницу в качестве модели.
В Views\Instructor\Index.cshtml добавьте заголовок Courses сразу после заголовка Office , как показано в следующем примере:
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
Затем добавьте новую ячейку сведений сразу после ячейки сведений о расположении офиса:
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.Courses)
{
@course.CourseID @: @course.Title <br />
}
}
</td>
<td>
@Html.ActionLink("Select", "Index", new { id = item.ID }) |
@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>
Запустите страницу "Индекс инструктора", чтобы просмотреть курсы, назначенные каждому инструктору.
Щелкните "Изменить" преподавателя, чтобы просмотреть страницу "Изменить ".
Измените некоторые задания курса и нажмите кнопку "Сохранить". Вносимые вами изменения отражаются на странице индекса.
Примечание. Подход, принятый здесь для редактирования данных курса инструктора, хорошо работает, когда существует ограниченное количество курсов. Для коллекций большего размера следовало бы применять другой пользовательский интерфейс и другой метод обновления.
Обновление DeleteConfirmed
В InstructorController.cs удалите DeleteConfirmed
метод и вставьте следующий код в его место.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
db.Instructors.Remove(instructor);
var department = db.Departments
.Where(d => d.InstructorID == id)
.SingleOrDefault();
if (department != null)
{
department.InstructorID = null;
}
db.SaveChanges();
return RedirectToAction("Index");
}
Этот код вносит следующее изменение:
- Если инструктор назначен администратором любого отдела, удаляет назначение инструктора из этого отдела. Без этого кода вы получите ошибку целостности ссылок, если вы попытались удалить инструктора, который был назначен администратором отдела.
Этот код не обрабатывает сценарий одного инструктора, назначенного администратором для нескольких отделов. В последнем руководстве вы добавите код, который предотвращает выполнение этого сценария.
Добавление расположения кабинета и курсов на страницу создания
В InstructorController.cs удалите HttpGet
и HttpPost
Create
методы, а затем добавьте следующий код в их место:
public ActionResult Create()
{
var instructor = new Instructor();
instructor.Courses = new List<Course>();
PopulateAssignedCourseData(instructor);
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "LastName,FirstMidName,HireDate,OfficeAssignment" )]Instructor instructor, string[] selectedCourses)
{
if (selectedCourses != null)
{
instructor.Courses = new List<Course>();
foreach (var course in selectedCourses)
{
var courseToAdd = db.Courses.Find(int.Parse(course));
instructor.Courses.Add(courseToAdd);
}
}
if (ModelState.IsValid)
{
db.Instructors.Add(instructor);
db.SaveChanges();
return RedirectToAction("Index");
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
Этот код аналогичен тому, что вы видели для методов Edit, за исключением того, что изначально не выбраны курсы. Метод HttpGet
Create
вызывает метод не потому, что могут быть выбраны курсы, а для предоставления пустой коллекции для foreach
цикла в представлении (в противном случае код представления вызовет PopulateAssignedCourseData
исключение null ссылок).
Метод HttpPost Create добавляет каждый выбранный курс в свойство навигации Courses перед кодом шаблона, который проверяет ошибки проверки и добавляет нового инструктора в базу данных. Курсы добавляются даже при наличии ошибок модели, чтобы при возникновении ошибок модели (например, пользователь заключил недопустимую дату), чтобы при повторном воспроизведении страницы с сообщением об ошибке все выбранные курсы были автоматически восстановлены.
Обратите внимание, что для добавления курсов в свойство навигации Courses
нужно инициализировать это свойство как пустую коллекцию:
instructor.Courses = new List<Course>();
Это можно сделать не только в коде контроллера, но и в модели Instructor, изменив метод получения свойств для автоматического создания коллекции, если она не существует, как показано в следующем примере:
private ICollection<Course> _courses;
public virtual ICollection<Course> Courses
{
get
{
return _courses ?? (_courses = new List<Course>());
}
set
{
_courses = value;
}
}
При подобном изменении свойства Courses
можно удалить код явной инициализации свойства в контроллере.
В Views\Instructor\Create.cshtml добавьте текстовое поле расположения офиса и флажки курса после поля даты найма и до кнопки "Отправить ".
<div class="form-group">
@Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.OfficeAssignment.Location)
@Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.ViewModels.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 в транзакцию, см . статью "Работа с транзакциями в MSDN".
Получение кода
Скачивание завершенного проекта
Дополнительные ресурсы
Ссылки на другие ресурсы Entity Framework можно найти в ASP.NET доступ к данным — рекомендуемые ресурсы.
Следующий шаг
Изучив это руководство, вы:
- Настраиваемые страницы курсов
- Добавлен офис на страницу инструкторов
- Добавлены курсы на страницу преподавателей
- Обновлено DeleteConfirmed
- Добавлено расположение офиса и курсы на страницу "Создание"
Перейдите к следующей статье, чтобы узнать, как реализовать асинхронную модель программирования.