ASP.NET MVC 응용 프로그램에서 Entity Framework를 사용 하 여 관련 데이터 업데이트 (6/10)Updating Related Data with the Entity Framework in an ASP.NET MVC Application (6 of 10)

만든 사람 Tom Dykstraby Tom Dykstra

Contoso 대학 샘플 웹 응용 프로그램은 Entity Framework 5 Code First 및 Visual Studio 2012을 사용 하 여 ASP.NET MVC 4 응용 프로그램을 만드는 방법을 보여 줍니다.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.

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. 이를 수행하기 위해 스캐폴드 코드는 컨트롤러 메서드 및 부서를 선택하기 위한 드롭다운 목록을 포함하는 만들기 및 편집 보기를 포함합니다.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 외래 키 속성을 설정 하며, Department 적절 한 엔터티로 탐색 속성을 로드 하는 데 필요한 모든 Entity Framework입니다 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 도우미에 전달 하 고 도우미는 개체에서 명명 된를 확인 하는 것을 알고 ViewBag SelectList DepartmentID 있습니다.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 메서드는 편집 중인 과정에 이미 할당 된 학과의 ID를 기반으로 선택한 항목을 설정 합니다.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.

Views\Course\Create.cshtml 에서 강조 표시 된 코드를 추가 하 여 제목 필드 앞에 새 강좌 번호 필드를 만듭니다.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")
}

Views\Course\Edit.cshtml, Views\Course\Delete.cshtmlViews\Course\Details.cshtml 에서 제목 필드 앞에 강좌 번호 필드를 추가 합니다.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

페이지에서 데이터를 변경하고 저장 을 클릭합니다.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:

  • 사용자가 사무실 할당을 지우면 원래 값이 있는 경우 엔터티를 제거 하 고 삭제 해야 합니다 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.
  • 사용자가 사무실 할당 값을 변경 하는 경우 기존 엔터티의 값을 변경 해야 합니다 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:

  • OfficeAssignment 탐색 속성에 대한 즉시 로드를 사용하여 데이터베이스에서 현재 Instructor 엔터티를 가져옵니다.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. TryUpdateModel 오버 로드를 사용 하면 포함 하려는 속성을 허용 목록 수 있습니다.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.

Views\Instructor\Edit.cshtml 에서 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). 사무실 위치 를 변경하고 저장 을 클릭합니다.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

및 엔터티 간의 관계 CourseInstructor 다 대 다입니다. 즉, 조인 테이블에 직접 액세스할 수 없습니다.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.

강사에게 할당된 강좌를 변경할 수 있도록 하는 UI는 확인란의 그룹입니다.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. Viewmodels 폴더에 AssignedCourseData.cs 를 만들고 기존 코드를 다음 코드로 바꿉니다.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. 강좌가 강사에 게 할당 되었는지 여부를 확인할 때 효율적인 조회를 만들기 위해 강사에 게 할당 된 강좌는 Hashset 컬렉션에 저장 됩니다.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.

다음으로 사용자가 저장 을 클릭할 때 실행되는 코드를 추가합니다.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. 허용 목록 오버 로드를 사용 하 고 있고 포함 목록에 있지 않기 때문에 TryUpdateModel 를 호출 하는 코드를 변경할 필요가 없습니다 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);
    }
}

Views\Instructor\Edit.cshtml 에서 필드의 요소 바로 뒤에 다음 강조 표시 된 코드를 추가 하 여 일련의 확인란을 사용 하 여 코스 필드를 추가 합니다 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.

Views\Instructor\Index.cshtml 에서 다음 예제와 같이 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>

그런 다음, 사무실 위치 정보 셀 바로 뒤에 새 정보 셀을 추가 합니다.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. 훨씬 큰 컬렉션의 경우 다른 UI 및 다른 업데이트 메서드가 필요합니다.For collections that are much larger, a different UI and a different updating method would be required.

Delete 메서드를 업데이트 합니다.Update 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.