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

от Tom Dykstra)

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

Note

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

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

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

Course_create_page

Instructor_edit_page_with_courses

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

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

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

В виевс\каурсе\креате.кштмл Добавьте выделенный код, чтобы создать новое поле номера курса перед полем 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")
}

В виевс\каурсе\едит.кштмл, виевс\каурсе\делете.кштмл и виевс\каурсе\детаилс.кштмл добавьте поле номера курса перед полем 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 метода, поэтому Where Single вместо выбора инструктора используются методы и.

Замените 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, используя значения из связывателя модели. Использование перегрузки трюпдатемодел позволяет список разрешений свойства, которые необходимо включить. Это предотвращает избыточное размещение, как описано во втором учебнике.

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    
  • Если расположение офиса пустое, устанавливает Instructor.OfficeAssignment свойство в значение null, чтобы связанная строка в OfficeAssignment таблице была удалена.

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    
  • Сохраняет изменения в базу данных.

В виевс\инструктор\едит.кштмл после 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

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

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

Instructor_edit_page_with_courses

Связь между 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 преподавателя. Чтобы создать эффективный поиск при проверке того, назначен ли курс инструктору, курсы, назначенные преподавателю, помещаются в коллекцию наборов хэширования . 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 свойство навигации. Вместо использования связывателя модели для обновления свойства навигации по курсам это можно сделать в новом UpdateInstructorCourses методе. Поэтому нужно исключить свойство Courses из привязки модели. Это не требует внесения каких-либо изменений в код, вызывающий трюпдатемодел , так как вы используете перегрузку список разрешений , а 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);
    }
}

В виевс\инструктор\едит.кштмл добавьте поле 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 сущности, которую вы передаете на страницу в качестве модели.

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

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

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

@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 ресурсы можно найти в конце последнего учебника этой серии.