Обновление связанных данных с помощью Entity Framework в приложении ASP.NET MVC (6 из 10)Updating Related Data with the Entity Framework in an ASP.NET MVC Application (6 of 10)

от Tom Dykstra)by Tom Dykstra

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

Пример веб-приложения Contoso университета демонстрирует создание приложений ASP.NET MVC 4 с помощью Entity Framework 5 Code First и Visual Studio 2012.The Contoso University sample web application demonstrates how to create ASP.NET MVC 4 applications using the Entity Framework 5 Code First and Visual Studio 2012. Сведения о серии руководств см. в первом руководстве серии.For information about the tutorial series, see the first tutorial in the series. Вы можете начать серию руководств с начала или скачать начальный проект для этой главы и начать отсюда.You can start the tutorial series from the beginning or download a starter project for this chapter and start here.

Note

Если проблема не устранена, Скачайте готовую главу и попытайтесь воспроизвести проблему.If you run into a problem you can't resolve, download the completed chapter and try to reproduce your problem. Как правило, решение проблемы можно найти, сравнив код с завершенным кодом.You can generally find the solution to the problem by comparing your code to the completed code. Некоторые распространенные ошибки и способы их устранения см. в разделе ошибки и обходные пути.For some common errors and how to solve them, see Errors and Workarounds.

В предыдущем учебнике отображаются связанные данные. в этом учебнике вы обновите связанные данные.In the previous tutorial you displayed related data; in this tutorial you'll update related data. Для большинства связей это можно сделать, обновив соответствующие поля внешнего ключа.For most relationships, this can be done by updating the appropriate foreign key fields. Для связей «многие ко многим» Entity Framework не раскрывает таблицу соединений напрямую, поэтому необходимо явно добавлять и удалять сущности в соответствующих свойствах навигации.For many-to-many relationships, the Entity Framework doesn't expose the join table directly, so you must explicitly add and remove entities to and from the appropriate navigation properties.

На следующих рисунках изображены страницы, с которыми вы будете работать.The following illustrations show the pages that you'll work with.

Course_create_page

Instructor_edit_page_with_courses

Настройка страниц создания и редактирования для курсовCustomize the Create and Edit Pages for Courses

Создаваемая сущность курса должна иметь связь с существующей кафедрой.When a new course entity is created, it must have a relationship to an existing department. Чтобы упростить эту задачу, шаблонный код включает методы контроллеров, а также представления "Create" (Создание) и "Edit" (Редактирование) с раскрывающимся списком для выбора кафедры.To facilitate this, the scaffolded code includes controller methods and Create and Edit views that include a drop-down list for selecting the department. Раскрывающийся список задает Course.DepartmentID свойство внешнего ключа, и это все Entity Framework потребности для загрузки свойства навигации Department с соответствующей сущностью Department.The drop-down list sets the Course.DepartmentID foreign key property, and that's all the Entity Framework needs in order to load the Department navigation property with the appropriate Department entity. Вы будете использовать этот шаблонный код, немного его изменив, чтобы добавить обработку ошибок и сортировку раскрывающегося списка.You'll use the scaffolded code, but change it slightly to add error handling and sort the drop-down list.

В CourseController.CSудалите четыре метода Edit и Create и замените их следующим кодом:In CourseController.cs, delete the four Edit and Create methods and replace them with the following code:

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.The PopulateDepartmentsDropDownList method gets a list of all departments sorted by name, creates a SelectList collection for a drop-down list, and passes the collection to the view in a ViewBag property. Этот метод принимает необязательный параметр selectedDepartment, позволяющий вызывающему коду указать элемент, который будет выбран при отрисовке раскрывающегося списка.The method accepts the optional selectedDepartment parameter that allows the calling code to specify the item that will be selected when the drop-down list is rendered. Представление передаст имя DepartmentID вспомогательному модулю DropDownList, а вспомогательный объект будет искать SelectList с именем DepartmentIDв объекте ViewBag.The view will pass the name DepartmentID to the DropDownList helper, and the helper then knows to look in the ViewBag object for a SelectList named DepartmentID.

Метод HttpGet Create вызывает метод PopulateDepartmentsDropDownList, не устанавливая выбранный элемент, так как для нового курса этот отдел еще не установлен:The HttpGet Create method calls the PopulateDepartmentsDropDownList method without setting the selected item, because for a new course the department is not established yet:

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

Метод HttpGet Edit задает выбранный элемент на основе идентификатора отдела, который уже назначен для изменяемого курса.The HttpGet Edit method sets the selected item, based on the ID of the department that is already assigned to the course being edited:

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

Методы HttpPost для Create и Edit также содержат код, который задает выбранный элемент при повторном отображении страницы после ошибки:The HttpPost methods for both Create and Edit also include code that sets the selected item when they redisplay the page after an error:

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

Этот код гарантирует, что при повторном отображении страницы для отображения сообщения об ошибке выбранный отдел останется выбранным.This code ensures that when the page is redisplayed to show the error message, whatever department was selected stays selected.

В виевс\каурсе\креате.кштмлДобавьте выделенный код, чтобы создать новое поле номера курса перед полем Title .In Views\Course\Create.cshtml, add the highlighted code to create a new course number field before the Title field. Как объясняется в предыдущем учебнике, поля первичного ключа по умолчанию не обопределяются, но этот первичный ключ имеет смысл, поэтому пользователь должен иметь возможность ввести значение ключа.As explained in an earlier tutorial, primary key fields aren't scaffolded by default, but this primary key is meaningful, so you want the user to be able to enter the key value.

@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 .In Views\Course\Edit.cshtml, Views\Course\Delete.cshtml, and Views\Course\Details.cshtml, add a course number field before the Title field. Так как это первичный ключ, он отображается, но его нельзя изменить.Because it's the primary key, it's displayed, but it can't be changed.

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

Запустите страницу создать (откройте страницу "Индекс курса" и щелкните создать) и введите данные для нового курса:Run the Create page (display the Course Index page and click Create New) and enter data for a new course:

Course_create_page

Нажмите кнопку Создать.Click Create. Откроется страница индекс курса с новым курсом, добавленным в список.The Course Index page is displayed with the new course added to the list. Название кафедры в списке страницы индекса поступает из свойства навигации, показывая, что связь установлена правильно.The department name in the Index page list comes from the navigation property, showing that the relationship was established correctly.

Course_Index_page_showing_new_course

Откройте страницу редактирования (откройте страницу "Индекс курса" и щелкните изменить в курсе).Run the Edit page (display the Course Index page and click Edit on a course).

Course_edit_page

Измените данные на странице и нажмите кнопку Save (Сохранить).Change data on the page and click Save. Отобразится страница индекса курса с обновленными данными курса.The Course Index page is displayed with the updated course data.

Добавление страницы редактирования для инструкторовAdding an Edit Page for Instructors

При редактировании записи преподавателя может потребоваться обновить назначенный преподавателю кабинет.When you edit an instructor record, you want to be able to update the instructor's office assignment. Сущность Instructor имеет связь "один к нулю" или "одна к одному" с сущностью OfficeAssignment, что означает, что необходимо выполнять следующие ситуации:The Instructor entity has a one-to-zero-or-one relationship with the OfficeAssignment entity, which means you must handle the following situations:

  • Если пользователь очищает назначение Office и изначально имел значение, необходимо удалить и удалить сущность OfficeAssignment.If the user clears the office assignment and it originally had a value, you must remove and delete the OfficeAssignment entity.
  • Если пользователь вводит значение назначения Office и изначально был пустым, необходимо создать новую сущность OfficeAssignment.If the user enters an office assignment value and it originally was empty, you must create a new OfficeAssignment entity.
  • Если пользователь изменяет значение назначения Office, необходимо изменить значение в существующей сущности OfficeAssignment.If the user changes the value of an office assignment, you must change the value in an existing OfficeAssignment entity.

Откройте InstructorController.CS и взгляните на метод HttpGet Edit:Open InstructorController.cs and look at the HttpGet Edit method:

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

Шаблон кода здесь не нужен.The scaffolded code here isn't what you want. Настраивает данные для раскрывающегося списка, но вам понадобится текстовое поле.It's setting up data for a drop-down list, but you what you need is a text box. Замените этот метод следующим кодом:Replace this method with the following code:

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

Этот код удаляет оператор ViewBag и добавляет безотлагательную загрузку для связанной сущности OfficeAssignment.This code drops the ViewBag statement and adds eager loading for the associated OfficeAssignment entity. Вы не можете выполнить безотлагательную загрузку с помощью метода Find, поэтому вместо выбора инструктора используются методы Where и Single.You can't perform eager loading with the Find method, so the Where and Single methods are used instead to select the instructor.

Замените метод HttpPost Edit следующим кодом.Replace the HttpPost Edit method with the following code. который обрабатывает обновления назначений Office:which handles office assignment updates:

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

Код делает следующее:The code does the following:

  • Получает текущую сущность Instructor из базы данных, используя безотложную загрузку для свойства навигации OfficeAssignment.Gets the current Instructor entity from the database using eager loading for the OfficeAssignment navigation property. Это то же самое, что и в методе HttpGet Edit.This is the same as what you did in the HttpGet Edit method.

  • Обновляет извлеченную сущность Instructor, используя значения из связывателя модели.Updates the retrieved Instructor entity with values from the model binder. Использование перегрузки трюпдатемодел позволяет список разрешений свойства, которые необходимо включить.The TryUpdateModel overload used enables you to whitelist the properties you want to include. Это предотвращает избыточное размещение, как описано во втором учебнике.This prevents over-posting, as explained in the second tutorial.

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    
  • Если расположение офиса пусто, присваивает свойству Instructor.OfficeAssignment значение null, чтобы связанная строка в таблице OfficeAssignment была удалена.If the office location is blank, sets the Instructor.OfficeAssignment property to null so that the related row in the OfficeAssignment table will be deleted.

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

В виевс\инструктор\едит.кштмлпосле элементов div для поля Дата найма добавьте новое поле для редактирования расположения офиса:In Views\Instructor\Edit.cshtml, after the div elements for the Hire Date field, add a new field for editing the office location:

<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>

Запустите страницу (выберите вкладку инструкторы и нажмите кнопку изменить на инструкторе).Run the page (select the Instructors tab and then click Edit on an instructor). Измените значение Office Location (Расположение кабинета) и нажмите кнопку Save (Сохранить).Change the Office Location and click Save.

Changing_the_office_location

Добавление назначений курсов на страницу редактирования инструкторовAdding Course Assignments to the Instructor Edit Page

Преподаватели могут вести любое число курсов.Instructors may teach any number of courses. Теперь вы улучшите страницу редактирования преподавателя, добавив возможность изменять назначения курсов с помощью группы флажков, как показано на следующем снимке экрана:Now you'll enhance the Instructor Edit page by adding the ability to change course assignments using a group of check boxes, as shown in the following screen shot:

Instructor_edit_page_with_courses

Отношение между сущностями Course и Instructor является "многие ко многим". Это означает, что у вас нет прямого доступа к таблице соединений.The relationship between the Course and Instructor entities is many-to-many, which means you do not have direct access to the join table. Вместо этого вы добавите и удалите сущности в свойстве навигации Instructor.Courses.Instead, you will add and remove entities to and from the Instructor.Courses navigation property.

Пользовательский интерфейс, позволяющий изменить назначенные для преподавателя курсы, представляет собой группу флажков.The UI that enables you to change which courses an instructor is assigned to is a group of check boxes. Отображается флажок для каждого курса в базе данных, а флажки для курсов, назначенных данному преподавателю, установлены.A check box for every course in the database is displayed, and the ones that the instructor is currently assigned to are selected. Пользователь может устанавливать и снимать флажки, изменяя назначения курсов.The user can select or clear check boxes to change course assignments. Если количество курсов было гораздо больше, вы, вероятно, захотите использовать другой метод представления данных в представлении, но для создания или удаления связей следует использовать один и тот же метод управления свойствами навигации.If the number of courses were much greater, you would probably want to use a different method of presenting the data in the view, but you'd use the same method of manipulating navigation properties in order to create or delete relationships.

Чтобы предоставить данные в представлении для списка флажков, нужно использовать класс модели представления.To provide data to the view for the list of check boxes, you'll use a view model class. Создайте AssignedCourseData.CS в папке ViewModels и замените имеющийся код следующим кодом:Create AssignedCourseData.cs in the ViewModels folder and replace the existing code with the following code:

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 следующим кодом.In InstructorController.cs, replace the HttpGet Edit method with the following code. Изменения выделены.The changes are highlighted.

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.The code adds eager loading for the Courses navigation property and calls the new PopulateAssignedCourseData method to provide information for the check box array using the AssignedCourseData view model class.

Код в методе PopulateAssignedCourseData считывает все Course сущности, чтобы загрузить список курсов с помощью класса модели представления.The code in the PopulateAssignedCourseData method reads through all Course entities in order to load a list of courses using the view model class. Для каждого курса код проверяет, существует ли этот курс в свойстве навигации Courses преподавателя.For each course, the code checks whether the course exists in the instructor's Courses navigation property. Чтобы создать эффективный поиск при проверке того, назначен ли курс инструктору, курсы, назначенные преподавателю, помещаются в коллекцию наборов хэширования .To create efficient lookup when checking whether a course is assigned to the instructor, the courses assigned to the instructor are put into a HashSet collection. Для курсов, назначенных преподавателем, свойству Assigned присвоено значение true.The Assigned property is set to true for courses the instructor is assigned. Представление будет использовать это свойство, чтобы определить, какие флажки нужно отображать как выбранные.The view will use this property to determine which check boxes must be displayed as selected. Наконец, список передается в представление в свойстве ViewBag.Finally, the list is passed to the view in a ViewBag property.

Добавьте код, выполняемый, когда пользователь нажимает кнопку Save (Сохранить).Next, add the code that's executed when the user clicks Save. Замените метод HttpPost Edit следующим кодом, который вызывает новый метод, который обновляет свойство навигации Courses сущности Instructor.Replace the HttpPost Edit method with the following code, which calls a new method that updates the Courses navigation property of the Instructor entity. Изменения выделены.The changes are highlighted.

[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.Since the view doesn't have a collection of Course entities, the model binder can't automatically update the Courses navigation property. Вместо использования связывателя модели для обновления свойства навигации по курсам это можно сделать в новом методе UpdateInstructorCourses.Instead of using the model binder to update the Courses navigation property, you'll do that in the new UpdateInstructorCourses method. Поэтому нужно исключить свойство Courses из привязки модели.Therefore you need to exclude the Courses property from model binding. Это не требует внесения каких-либо изменений в код, вызывающий трюпдатемодел , так как используется перегрузка список разрешений , а Courses нет в списке включения.This doesn't require any change to the code that calls TryUpdateModel because you're using the whitelisting overload and Courses isn't in the include list.

Если флажки не выбраны, код в UpdateInstructorCourses инициализирует Courses свойство навигации пустой коллекцией:If no check boxes were selected, the code in UpdateInstructorCourses initializes the Courses navigation property with an empty collection:

if (selectedCourses == null)
{
    instructorToUpdate.Courses = new List<Course>();
    return;
}

После этого код в цикле проходит по всем курсам в базе данных и сравнивает каждый из них с теми, которые сейчас назначены преподавателю, в противоположность тем, которые были выбраны в представлении.The code then loops through all courses in the database and checks each course against the ones currently assigned to the instructor versus the ones that were selected in the view. Чтобы упростить эффективную подстановку, последние две коллекции хранятся в объектах HashSet.To facilitate efficient lookups, the latter two collections are stored in HashSet objects.

Если флажок для курса был установлен, но курс отсутствует в свойстве навигации Instructor.Courses, этот курс добавляется в коллекцию в свойстве навигации.If the check box for a course was selected but the course isn't in the Instructor.Courses navigation property, the course is added to the collection in the navigation property.

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
    if (!instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Add(course);
    }
}

Если флажок для курса не был установлен, но курс присутствует в свойстве навигации Instructor.Courses, этот курс удаляется из свойства навигации.If the check box for a course wasn't selected, but the course is in the Instructor.Courses navigation property, the course is removed from the navigation property.

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Remove(course);
    }
}

В виевс\инструктор\едит.кштмлдобавьте поле Courses с массивом флажков, добавив следующий выделенный код сразу после элементов div для поля OfficeAssignment:In Views\Instructor\Edit.cshtml, add a Courses field with an array of check boxes by adding the following highlighted code immediately after the div elements for the OfficeAssignment field:

@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 с тремя столбцами.This code creates an HTML table that has three columns. Каждый столбец содержит флажок, за которым идет заголовок с номером и названием курса.In each column is a check box followed by a caption that consists of the course number and title. Все флажки имеют одно и то же имя ("selectedCourses"), которое информирует связыватель модели о том, что они должны рассматриваться как группа.The check boxes all have the same name ("selectedCourses"), which informs the model binder that they are to be treated as a group. Для атрибута value каждого флажка задано значение CourseID. при публикации страницы, связыватель модели передает массив контроллеру, который состоит из CourseID значений только выбранных флажков.The value attribute of each check box is set to the value of CourseID. When the page is posted, the model binder passes an array to the controller that consists of the CourseID values for only the check boxes which are selected.

При первоначальном отображении флажков, которые предназначены для курсов, назначенных преподавателю, имеют checked атрибуты, которые выбирают их (отображает отмеченные).When the check boxes are initially rendered, those that are for courses assigned to the instructor have checked attributes, which selects them (displays them checked).

После изменения назначений курсов необходимо проверить изменения при возврате сайта на страницу Index.After changing course assignments, you'll want to be able to verify the changes when the site returns to the Index page. Поэтому необходимо добавить столбец в таблицу на этой странице.Therefore, you need to add a column to the table in that page. В этом случае вам не нужно использовать объект ViewBag, так как отображаемая информация уже находится в свойстве навигации Courses сущности Instructor, которую вы передаете на страницу в качестве модели.In this case you don't need to use the ViewBag object, because the information you want to display is already in the Courses navigation property of the Instructor entity that you're passing to the page as the model.

В виевс\инструктор\индекс.кштмлдобавьте заголовок курсов сразу после заголовка Office , как показано в следующем примере:In Views\Instructor\Index.cshtml, add a Courses heading immediately following the Office heading, as shown in the following example:

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

Затем добавьте новую ячейку сведений сразу после ячейки сведений о местонахождении Office:Then add a new detail cell immediately following the office location detail cell:

@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> 
}

Чтобы просмотреть курсы, назначенные каждому инструктору, выполните страницу " индекс инструктора ".Run the Instructor Index page to see the courses assigned to each instructor:

Instructor_index_page

Щелкните изменить на лекторе, чтобы открыть страницу изменение.Click Edit on an instructor to see the Edit page.

Instructor_edit_page_with_courses

Измените некоторые назначения курсов и нажмите кнопку сохранить.Change some course assignments and click Save. Вносимые вами изменения отражаются на странице индекса.The changes you make are reflected on the Index page.

Примечание. подход, выполняемый для изменения данных курса преподавателя, хорошо работает при наличии ограниченного числа курсов.Note: The approach taken to edit instructor course data works well when there is a limited number of courses. Для коллекций большего размера следовало бы применять другой пользовательский интерфейс и другой метод обновления.For collections that are much larger, a different UI and a different updating method would be required.

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

Измените код в методе HttpPost DELETE, чтобы запись назначения Office (если она есть) была удалена при удалении преподавателя:Change the code in the HttpPost Delete method so the office assignment record (if any) is deleted when the instructor is deleted:

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

При попытке удалить лектора, назначенного Отделу от имени администратора, вы получите ошибку ссылочной целостности.If you try to delete an instructor who is assigned to a department as administrator, you'll get a referential integrity error. См. текущую версию этого руководства для дополнительного кода, который будет автоматически удалять лектора из любого отдела, в котором лектор назначен в качестве администратора.See the current version of this tutorial for additional code that will automatically remove the instructor from any department where the instructor is assigned as an administrator.

СводкаSummary

Вы завершили это введение в работе с связанными данными.You have now completed this introduction to working with related data. До сих пор в этих учебниках вы выполнили полный спектр операций CRUD, но вы не работали с проблемами параллелизма.So far in these tutorials you've done a full range of CRUD operations, but you haven't dealt with concurrency issues. В следующем учебнике будет представлен раздел параллелизма, описаны варианты его обработки и добавлена обработка параллелизма для кода CRUD, который вы уже написали для одного типа сущности.The next tutorial will introduce the topic of concurrency, explain options for handling it, and add concurrency handling to the CRUD code you've already written for one entity type.

Ссылки на другие Entity Framework ресурсы можно найти в конце последнего учебника этой серии.Links to other Entity Framework resources, can be found at the end of the last tutorial in this series.