Поделиться через


Обновление связанных данных с помощью Entity Framework в приложении MVC ASP.NET (6 из 10)

Том Дайкстра

Пример веб-приложения Contoso University демонстрирует создание ASP.NET приложений MVC 4 с помощью Entity Framework 5 Code First и Visual Studio 2012. Сведения о серии руководств см. в первом руководстве серии.

Примечание.

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

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

На следующих рисунках изображены страницы, с которыми вы будете работать.

Снимок экрана: страница

Снимок экрана: страница

Настройка страниц создания и редактирования для курсов

Создаваемая сущность курса должна иметь связь с существующей кафедрой. Чтобы упростить эту задачу, шаблонный код включает методы контроллеров, а также представления "Create" (Создание) и "Edit" (Редактирование) с раскрывающимся списком для выбора кафедры. Раскрывающийся список задает свойство внешнего ключа Course.DepartmentID, и это все, что нужно Entity Framework для загрузки свойства навигации Department с соответствующей сущностью Department. Вы будете использовать этот шаблонный код, немного его изменив, чтобы добавить обработку ошибок и сортировку раскрывающегося списка.

В CourseController.cs удалите четыре Edit и методы и Create замените их следующим кодом:

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 (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException 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)
{
   Course course = db.Courses.Find(id);
   PopulateDepartmentsDropDownList(course.DepartmentID);
   return View(course);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
    [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
    Course course)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(course).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException 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);
}

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);
} 

Метод PopulateDepartmentsDropDownList получает список всех отделов, отсортированных по имени, создает SelectList коллекцию для раскрывающегося списка и передает коллекцию в представление в свойстве ViewBag . Этот метод принимает необязательный параметр selectedDepartment, позволяющий вызывающему коду указать элемент, который будет выбран при отрисовке раскрывающегося списка. Представление передает имя DepartmentID DropDownList вспомогательному элементу, а помощник затем знает, как выглядеть в объекте ViewBag для именованного.SelectList DepartmentID

Метод HttpGet Create вызывает PopulateDepartmentsDropDownList метод без настройки выбранного элемента, так как для нового курса отдел еще не установлен:

public ActionResult Create()
{
    PopulateDepartmentsDropDownList();
    return View();
}

Метод HttpGet Edit задает выбранный элемент на основе идентификатора отдела, который уже назначен курсу для редактирования:

public ActionResult Edit(int id)
{
    Course course = db.Courses.Find(id);
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

Методы HttpPost для обоих Create элементов, Edit а также код, который задает выбранный элемент при повторном воспроизведении страницы после ошибки:

catch (DataException /* dex */)
{
    //Log the error (uncomment dex variable name after DataException 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);

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

В Views\Course\Create.cshtml добавьте выделенный код, чтобы создать новое поле номера курса перед полем Title . Как описано в предыдущем руководстве, поля первичного ключа по умолчанию не являются шаблонами, но этот первичный ключ имеет смысл, поэтому вы хотите, чтобы пользователь мог ввести значение ключа.

@model ContosoUniversity.Models.Course

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Course</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.CourseID)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.CourseID)
            @Html.ValidationMessageFor(model => model.CourseID)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Credits)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Credits)
            @Html.ValidationMessageFor(model => model.Credits)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.DepartmentID, "Department")
        </div>
        <div class="editor-field">
            @Html.DropDownList("DepartmentID", String.Empty)
            @Html.ValidationMessageFor(model => model.DepartmentID)
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

В представлениях\Course\Edit.cshtml, Views\Course\Delete.cshtml и Views\Course\Details.cshtml добавьте поле номера курса перед полем Title . Так как это первичный ключ, он отображается, но его нельзя изменить.

<div class="editor-label">
    @Html.LabelFor(model => model.CourseID)
</div>
<div class="editor-field">
    @Html.DisplayFor(model => model.CourseID)
</div>

Запустите страницу "Создать" (отобразите страницу индекса курса и нажмите кнопку "Создать") и введите данные для нового курса:

Course_create_page

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

Course_Index_page_showing_new_course

Запустите страницу "Изменить" (отобразите страницу "Индекс курса" и нажмите кнопку "Изменить" для курса).

Course_edit_page

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

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

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

  • Если пользователь очищает назначение office и изначально имеет значение, необходимо удалить и удалить OfficeAssignment сущность.
  • Если пользователь вводит значение назначения office и изначально был пустым, необходимо создать новую OfficeAssignment сущность.
  • Если пользователь изменяет значение назначения office, необходимо изменить значение существующей OfficeAssignment сущности.

Откройте InstructorController.cs и просмотрите HttpGet Edit метод:

public ActionResult Edit(int id = 0)
{
    Instructor instructor = db.Instructors.Find(id);
    if (instructor == null)
    {
        return HttpNotFound();
    }
    ViewBag.InstructorID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.InstructorID);
    return View(instructor);
}

Шаблонный код здесь не является нужным. Он настраивает данные для раскрывающегося списка, но вам нужно текстовое поле. Замените этот метод следующим кодом:

public ActionResult Edit(int id)
{
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Where(i => i.InstructorID == id)
        .Single();
    return View(instructor);
}

Этот код удаляет инструкцию ViewBag и добавляет страстную загрузку связанной OfficeAssignment сущности. Вы не можете выполнять страстную загрузку с Find помощью метода, поэтому Single Where вместо этого используются методы для выбора инструктора.

Замените HttpPost Edit метод следующим кодом. который обрабатывает обновления назначения office:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, FormCollection formCollection)
{
   var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Where(i => i.InstructorID == id)
       .Single();

   if (TryUpdateModel(instructorToUpdate, "",
      new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
   {
      try
      {
         if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
         {
            instructorToUpdate.OfficeAssignment = null;
         }

         db.Entry(instructorToUpdate).State = EntityState.Modified;
         db.SaveChanges();

         return RedirectToAction("Index");
      }
      catch (DataException /* dex */)
      {
         //Log the error (uncomment dex variable name after DataException 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.");
      }
   }
   ViewBag.InstructorID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", id);
   return View(instructorToUpdate);
}

Код делает следующее:

  • Получает текущую сущность 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="editor-label">
    @Html.LabelFor(model => model.OfficeAssignment.Location)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.OfficeAssignment.Location)
    @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
</div>

Запустите страницу (перейдите на вкладку "Инструкторы" и нажмите кнопку "Изменить " для инструктора). Измените значение Office Location (Расположение кабинета) и нажмите кнопку Save (Сохранить).

Changing_the_office_location

Добавление заданий курса на страницу редактирования инструктора

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

Снимок экрана: страница

Связь между 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)
{
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.InstructorID == id)
        .Single();
    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 (Сохранить). Замените HttpPost Edit метод следующим кодом, который вызывает новый метод, который обновляет Courses свойство навигации сущности Instructor . Изменения выделены.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, FormCollection formCollection, string[] selectedCourses)
{
   var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Include(i => i.Courses)
       .Where(i => i.InstructorID == 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.Entry(instructorToUpdate).State = EntityState.Modified;
         db.SaveChanges();

         return RedirectToAction("Index");
      }
      catch (DataException /* dex */)
      {
         //Log the error (uncomment dex variable name after DataException 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);
         }
      }
   }
}

Так как представление не содержит коллекцию сущностей 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 :

@model ContosoUniversity.Models.Instructor

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Instructor</legend>

        @Html.HiddenFor(model => model.InstructorID)

        <div class="editor-label">
            @Html.LabelFor(model => model.LastName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.FirstMidName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.FirstMidName)
            @Html.ValidationMessageFor(model => model.FirstMidName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.HireDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.HireDate)
            @Html.ValidationMessageFor(model => model.HireDate)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.OfficeAssignment.Location)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.OfficeAssignment.Location)
            @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
        </div>

        <div class="editor-field">
    <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>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Этот код создает таблицу HTML с тремя столбцами. Каждый столбец содержит флажок, за которым идет заголовок с номером и названием курса. Все флажки имеют то же имя ("selectedCourses"), которое сообщает привязщику модели, что они должны рассматриваться как группа. Атрибут value каждого флажка имеет значение CourseID. "Когда страница размещена", привязка модели передает массив контроллеру, состоящему из CourseID значений только выбранных флажков.

Когда флажки изначально отображаются, те, которые предназначены для курсов, назначенных инструктору, имеют checked атрибуты, которые выбирают их (отображает их).

После изменения заданий курса вы захотите проверить изменения, когда сайт возвращается на страницу Index . Поэтому необходимо добавить столбец в таблицу на этой странице. В этом случае не нужно использовать ViewBag объект, так как сведения, которые вы хотите отобразить, уже находится в Courses свойстве Instructor навигации сущности, которую вы передаете на страницу в качестве модели.

В Views\Instructor\Index.cshtml добавьте заголовок Courses сразу после заголовка Office , как показано в следующем примере:

<tr> 
    <th></th> 
    <th>Last Name</th> 
    <th>First Name</th> 
    <th>Hire Date</th> 
    <th>Office</th>
    <th>Courses</th>
</tr>

Затем добавьте новую ячейку сведений сразу после ячейки сведений о расположении офиса:

@model ContosoUniversity.ViewModels.InstructorIndexData

@{
    ViewBag.Title = "Instructors";
}

<h2>Instructors</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th></th>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Hire Date</th>
        <th>Office</th>
        <th>Courses</th>
    </tr>
    @foreach (var item in Model.Instructors)
    {
        string selectedRow = "";
        if (item.InstructorID == ViewBag.InstructorID)
        {
            selectedRow = "selectedrow";
        } 
        <tr class="@selectedRow" valign="top">
            <td>
                @Html.ActionLink("Select", "Index", new { id = item.InstructorID }) | 
                @Html.ActionLink("Edit", "Edit", new { id = item.InstructorID }) | 
                @Html.ActionLink("Details", "Details", new { id = item.InstructorID }) | 
                @Html.ActionLink("Delete", "Delete", new { id = item.InstructorID })
            </td>
            <td>
                @item.LastName
            </td>
            <td>
                @item.FirstMidName
            </td>
            <td>
                @String.Format("{0:d}", item.HireDate)
            </td>
            <td>
                @if (item.OfficeAssignment != null)
                { 
                    @item.OfficeAssignment.Location  
                }
            </td>
            <td>
                @{
                foreach (var course in item.Courses)
                {
                    @course.CourseID @:  @course.Title <br />
                }
                }
            </td>
        </tr> 
    }
</table>

@if (Model.Courses != null)
{ 
    <h3>Courses Taught by Selected Instructor</h3> 
    <table>
        <tr>
            <th></th>
            <th>ID</th>
            <th>Title</th>
            <th>Department</th>
        </tr>

        @foreach (var item in Model.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == ViewBag.CourseID)
            {
                selectedRow = "selectedrow";
            } 
        
            <tr class="@selectedRow">

                <td>
                    @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr> 
        }

    </table> 
}
@if (Model.Enrollments != null)
{ 
    <h3>Students Enrolled in Selected Course</h3> 
    <table>
        <tr>
            <th>Name</th>
            <th>Grade</th>
        </tr>
        @foreach (var item in Model.Enrollments)
        { 
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr> 
        }
    </table> 
}

Запустите страницу "Индекс инструктора", чтобы просмотреть курсы, назначенные каждому инструктору:

Instructor_index_page

Щелкните "Изменить" преподавателя, чтобы просмотреть страницу "Изменить ".

Instructor_edit_page_with_courses

Измените некоторые задания курса и нажмите кнопку "Сохранить". Вносимые вами изменения отражаются на странице индекса.

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

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

Измените код в методе HttpPost Delete, чтобы запись назначения office (при наличии) удаляется при удалении инструктора:

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
   Instructor instructor = db.Instructors
     .Include(i => i.OfficeAssignment)
     .Where(i => i.InstructorID == id)
     .Single();

   instructor.OfficeAssignment = null;
   db.Instructors.Remove(instructor);
   db.SaveChanges();
   return RedirectToAction("Index");
}

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

Итоги

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

Ссылки на другие ресурсы Entity Framework можно найти в конце последнего учебника в этой серии.