Руководство. обновление связанных данных с помощью EF в приложении ASP.NET MVC

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

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

Course_create_page

Instructor_edit_page_with_courses

Изменение инструкторов с помощью курсов

Изучив это руководство, вы:

  • Настройка страниц курсов
  • Страница добавления Office в инструкторы
  • Страница добавления курсов в инструкторы
  • Обновление Делетеконфирмед
  • Добавление расположения кабинета и курсов на страницу создания

предварительные требования

Настройка страниц курсов

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

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

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

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID")]Course course)
{
    try
    {
        if (ModelState.IsValid)
        {
            db.Courses.Add(course);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    }
    catch (RetryLimitExceededException /* dex */)
    {
        //Log the error (uncomment dex variable name and add a line here to write a log.)
        ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Course course = db.Courses.Find(id);
    if (course == null)
    {
        return HttpNotFound();
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var courseToUpdate = db.Courses.Find(id);
    if (TryUpdateModel(courseToUpdate, "",
       new string[] { "Title", "Credits", "DepartmentID" }))
    {
        try
        {
            db.SaveChanges();

            return RedirectToAction("Index");
        }
        catch (RetryLimitExceededException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
    }
    PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
    return View(courseToUpdate);
}

private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
    var departmentsQuery = from d in db.Departments
                           orderby d.Name
                           select d;
    ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
} 

Добавьте в начало файла следующую инструкцию using:

using System.Data.Entity.Infrastructure;

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

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

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

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

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Course course = db.Courses.Find(id);
    if (course == null)
    {
        return HttpNotFound();
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

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

catch (RetryLimitExceededException /* dex */)
{
    //Log the error (uncomment dex variable name and add a line here to write a log.)
    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);

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

Представления курсов уже обрабатываются с помощью раскрывающихся списков для поля "Отдел", но не требуется заголовку DepartmentID для этого поля, поэтому сделайте следующее выделенное изменение в файле виевс\каурсе\креате.кштмл , чтобы изменить заголовок.

@model ContosoUniversity.Models.Course

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Course</h4>
        <hr />
        @Html.ValidationSummary(true)

        <div class="form-group">
            @Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.CourseID)
                @Html.ValidationMessageFor(model => model.CourseID)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Credits, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Credits)
                @Html.ValidationMessageFor(model => model.Credits)
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-2" for="DepartmentID">Department</label>
            <div class="col-md-10">
                @Html.DropDownList("DepartmentID", String.Empty)
                @Html.ValidationMessageFor(model => model.DepartmentID)
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

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

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

Внесите те же изменения в виевс\каурсе\едит.кштмл.

Как правило, механизм формирования шаблонов не формирует первичный ключ, поскольку значение ключа создается базой данных и не может быть изменено и не является значимым значением для отображения пользователям. Для сущностей Course шаблон включает текстовое поле для поля CourseID, поскольку оно понимает, что атрибут DatabaseGeneratedOption.None означает, что пользователь должен иметь возможность ввести значение первичного ключа. Но это не понимает, так как число является значимым для просмотра в других представлениях, поэтому его необходимо добавить вручную.

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

<div class="form-group">
    @Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.DisplayFor(model => model.CourseID)
    </div>
</div>

В представлении редактирования уже есть скрытое поле (Html.HiddenFor вспомогательное приложение) для номера курса. Добавление вспомогательного метода HTML. лабелфор не устраняет потребность в скрытом поле, так как не приводит к включению номера курса в отправленные данные, когда пользователь нажимает кнопку сохранить на странице Правка.

В виевс\каурсе\делете.кштмл и виевс\каурсе\детаилс.кштмлизмените заголовок названия отдела с "имя" на "Отдел" и добавьте поле "номер курса" перед полем " название ".

<dt>
    Department
</dt>

<dd>
    @Html.DisplayFor(model => model.Department.Name)
</dd>

<dt>
    @Html.DisplayNameFor(model => model.CourseID)
</dt>

<dd>
    @Html.DisplayFor(model => model.CourseID)
</dd>

Запустите страницу создать (откройте страницу "Индекс курса" и щелкните создать) и введите данные для нового курса:

Значение Параметр
Number Введите 1000.
Title Введите вэтом случае.
Баллы Введите 4.
отдел; Выберите математика.

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

Откройте страницу редактирования (откройте страницу "Индекс курса" и щелкните изменить в курсе).

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

Страница добавления Office в инструкторы

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

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

Откройте InstructorController.CS и взгляните на метод HttpGet Edit:

{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors.Find(id);
    if (instructor == null)
    {
        return HttpNotFound();
    }
    ViewBag.ID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.ID);
    return View(instructor);
}

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

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Where(i => i.ID == id)
        .Single();
    if (instructor == null)
    {
        return HttpNotFound();
    }
    return View(instructor);
}

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

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

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Where(i => i.ID == id)
       .Single();

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

          db.SaveChanges();

          return RedirectToAction("Index");
       }
       catch (RetryLimitExceededException /* dex */)
      {
         //Log the error (uncomment dex variable name and add a line here to write a log.
         ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
      }
   }
   return View(instructorToUpdate);
}

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

Выберите Показывать возможные исправления, а затем с помощью System. Data. Entity. Infrastructure .

Устранить исключение повторных попыток

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

  • Изменяет имя метода на EditPost, поскольку сигнатура теперь совпадает с методом HttpGet (атрибут ActionName указывает, что URL-адрес/едит/по-прежнему используется).

  • Получает текущую сущность 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="form-group">
    @Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.OfficeAssignment.Location)
        @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
    </div>
</div>

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

Страница добавления курсов в инструкторы

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

Отношение между сущностями Course и Instructor является "многие ко многим". Это означает, что у вас нет прямого доступа к свойствам внешнего ключа, которые находятся в таблице соединение. Вместо этого можно добавлять и удалять сущности в свойстве навигации Instructor.Courses.

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

Чтобы предоставить данные в представлении для списка флажков, нужно использовать класс модели представления. Создайте AssignedCourseData.CS в папке ViewModels и замените имеющийся код следующим кодом:

namespace ContosoUniversity.ViewModels
{
    public class AssignedCourseData
    {
        public int CourseID { get; set; }
        public string Title { get; set; }
        public bool Assigned { get; set; }
    }
}

В InstructorController.CSзамените метод HttpGet Edit следующим кодом. Изменения выделены.

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.ID == id)
        .Single();
    PopulateAssignedCourseData(instructor);
    if (instructor == null)
    {
        return HttpNotFound();
    }
    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 (Сохранить). Замените метод EditPost следующим кодом, который вызывает новый метод, который обновляет свойство навигации Courses сущности Instructor. Изменения выделены.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int? id, string[] selectedCourses)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Include(i => i.Courses)
       .Where(i => i.ID == id)
       .Single();

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

            UpdateInstructorCourses(selectedCourses, instructorToUpdate);

            db.SaveChanges();

            return RedirectToAction("Index");
        }
        catch (RetryLimitExceededException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
    }
    PopulateAssignedCourseData(instructorToUpdate);
    return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
   if (selectedCourses == null)
   {
      instructorToUpdate.Courses = new List<Course>();
      return;
   }
 
   var selectedCoursesHS = new HashSet<string>(selectedCourses);
   var instructorCourses = new HashSet<int>
       (instructorToUpdate.Courses.Select(c => c.CourseID));
   foreach (var course in db.Courses)
   {
      if (selectedCoursesHS.Contains(course.CourseID.ToString()))
      {
         if (!instructorCourses.Contains(course.CourseID))
         {
            instructorToUpdate.Courses.Add(course);
         }
      }
      else
      {
         if (instructorCourses.Contains(course.CourseID))
         {
            instructorToUpdate.Courses.Remove(course);
         }
      }
   }
}

Сигнатура метода теперь отличается от метода HttpGet Edit, поэтому имя метода меняется с EditPost обратно на Edit.

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

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;

                    foreach (var course in courses)
                    {
                        if (cnt++ % 3 == 0)
                        {
                            @:</tr><tr>
                        }
                        @:<td>
                            <input type="checkbox"
                               name="selectedCourses"
                               value="@course.CourseID"
                               @(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
                               @course.CourseID @:  @course.Title
                        @:</td>
                    }
                    @:</tr>
                }
        </table>
    </div>
</div>

После вставки кода, если разрывы строк и отступы не выглядят так, как показано здесь, вручную исправьте все, чтобы оно выглядело так, как вы видите здесь. Выравнивать отступы необязательно, однако строки @</tr><tr>, @:<td>, @:</td> и @</tr> должны находиться на одной строке, как показано здесь. В противном случае возникает ошибка времени выполнения.

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

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

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

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

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

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

<td>
    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
</td>
<td>
    @{
        foreach (var course in item.Courses)
        {
            @course.CourseID @:  @course.Title <br />
        }
    }
</td>
<td>
    @Html.ActionLink("Select", "Index", new { id = item.ID }) |
    @Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
    @Html.ActionLink("Details", "Details", new { id = item.ID }) |
    @Html.ActionLink("Delete", "Delete", new { id = item.ID })
</td>

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

Щелкните изменить на лекторе, чтобы открыть страницу изменение.

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

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

Обновление Делетеконфирмед

В InstructorController.CSудалите метод DeleteConfirmed и вставьте следующий код на его место.

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

   db.Instructors.Remove(instructor);

    var department = db.Departments
        .Where(d => d.InstructorID == id)
        .SingleOrDefault();
    if (department != null)
    {
        department.InstructorID = null;
    }

   db.SaveChanges();
   return RedirectToAction("Index");
}

Этот код вносит следующие изменения:

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

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

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

В InstructorController.CSудалите методы HttpGet и HttpPost Create, а затем добавьте следующий код в их место:

public ActionResult Create()
{
    var instructor = new Instructor();
    instructor.Courses = new List<Course>();
    PopulateAssignedCourseData(instructor);
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "LastName,FirstMidName,HireDate,OfficeAssignment" )]Instructor instructor, string[] selectedCourses)
{
    if (selectedCourses != null)
    {
        instructor.Courses = new List<Course>();
        foreach (var course in selectedCourses)
        {
            var courseToAdd = db.Courses.Find(int.Parse(course));
            instructor.Courses.Add(courseToAdd);
        }
    }
    if (ModelState.IsValid)
    {
        db.Instructors.Add(instructor);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

Этот код аналогичен тому, который вы видели для методов редактирования, за исключением того, что изначально не выбраны курсы. Метод HttpGet Create вызывает метод PopulateAssignedCourseData, не так как в нем могут быть выбраны курсы, но чтобы предоставить пустую коллекцию для цикла foreach в представлении (в противном случае код представления выдаст исключение null reference).

Метод Create HttpPost добавляет каждый выбранный курс в свойство навигации по курсам перед кодом шаблона, который проверяет наличие ошибок проверки и добавляет новый лектор в базу данных. Курсы добавляются, даже если существуют ошибки модели, поэтому при возникновении ошибок модели (например, пользователь с ключом "Недопустимая дата"), поэтому при повторном отображении страницы с сообщением об ошибке все выбранные параметры курса восстанавливаются автоматически.

Обратите внимание, что для добавления курсов в свойство навигации Courses нужно инициализировать это свойство как пустую коллекцию:

instructor.Courses = new List<Course>();

Это можно сделать не только в коде контроллера, но и в модели Instructor, изменив метод получения свойств для автоматического создания коллекции, если она не существует, как показано в следующем примере:

private ICollection<Course> _courses;
public virtual ICollection<Course> Courses 
{ 
    get
    {
        return _courses ?? (_courses = new List<Course>());
    }
    set
    {
        _courses = value;
    } 
}

При подобном изменении свойства Courses можно удалить код явной инициализации свойства в контроллере.

В виевс\инструктор\креате.кштмлдобавьте текстовое поле "Расположение офиса" и флажок "курс" после поля "Дата найма" и перед кнопкой " Отправить ".

<div class="form-group">
    @Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.OfficeAssignment.Location)
        @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
    </div>
</div>

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;

                    foreach (var course in courses)
                    {
                        if (cnt++ % 3 == 0)
                        {
                            @:</tr><tr>
                        }
                        @:<td>
                            <input type="checkbox"
                               name="selectedCourses"
                               value="@course.CourseID"
                               @(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
                               @course.CourseID @:  @course.Title
                        @:</td>
                    }
                    @:</tr>
                }
        </table>
    </div>
</div>

После вставки кода исправьте разрывы строк и отступы, как это было сделано ранее на странице Редактирование.

Запустите страницу Создание и добавьте инструктор.

Обработка транзакций

Как описано в руководстве по работе с базовыми ФУНКЦИЯМИ CRUD, по умолчанию Entity Framework неявно реализует транзакции. Для сценариев, в которых требуется больший контроль, например, если вы хотите включить операции, выполняемые вне Entity Framework в транзакции, см. статью Работа с транзакциями в MSDN.

Получение кода

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

Дополнительные ресурсы

Ссылки на другие ресурсы Entity Framework можно найти в материалах, рекомендуемых для доступа к данным ASP.NET.

Следующий шаг

Изучив это руководство, вы:

  • Страницы настроенных курсов
  • Страница добавления Office в инструкторы
  • Добавлены курсы на страницу инструкторов
  • Обновленный Делетеконфирмед
  • Добавление расположения и курсов Office на страницу создания

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