자습서: ASP.NET MVC 앱에서 EF로 관련 데이터 업데이트
이전 자습서에서는 관련 데이터를 표시했습니다. 이 자습서에서는 관련 데이터를 업데이트합니다. 대부분의 관계에서는 외래 키 필드 또는 탐색 속성을 업데이트하여 이 작업을 수행할 수 있습니다. 다 대 다 관계의 경우 Entity Framework는 조인 테이블을 직접 노출하지 않으므로 적절한 탐색 속성에서 엔터티를 추가하고 제거합니다.
다음 그림에서는 사용할 일부 페이지를 보여 줍니다.
이 자습서에서는 다음을 수행합니다.
- 과정 페이지 사용자 지정
- 강사에 사무실 추가 페이지
- 강사 페이지에 강좌 추가
- DeleteConfirmed 업데이트
- 만들기 페이지에 사무실 위치 및 강좌 추가
필수 조건
과정 페이지 사용자 지정
새 강좌 엔터티가 만들어질 때 기존 부서에 대한 관계가 있어야 합니다. 이를 수행하기 위해 스캐폴드 코드는 컨트롤러 메서드 및 부서를 선택하기 위한 드롭다운 목록을 포함하는 만들기 및 편집 보기를 포함합니다. 드롭다운 목록은 Course.DepartmentID
외래 키 속성을 설정하고, 이는 적절한 Department
엔터티로 Department
탐색 속성을 로드하기 위해 필요한 모든 Entity Framework입니다. 스캐폴드 코드를 사용하지만 오류 처리를 추가하고 드롭다운 목록을 정렬하도록 약간 변경합니다.
CourseController.cs 4 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
매개 변수를 허용합니다. 보기는 DropDownList 도우미에 이름을 DepartmentID
전달하고 도우미는 명명된 DepartmentID
개체 SelectList
를 ViewBag
찾는 것을 알고 있습니다.
이 메서드는 HttpGet
Create
선택한 항목을 설정하지 않고 메서드를 호출 PopulateDepartmentsDropDownList
합니다. 새 과정의 경우 부서가 아직 설정되지 않았기 때문입니다.
public ActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
이 메서드는 HttpGet
Edit
편집 중인 과정에 이미 할당된 부서의 ID에 따라 선택한 항목을 설정합니다.
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 캡션을 원하지 않으므로 Views\Course\Create.cshtml 파일을 다음과 같이 강조 표시하여 캡션을 변경합니다.
@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")
}
Views\Course\Edit.cshtml에서도 동일하게 변경합니다.
일반적으로 스캐폴더는 기본 키를 스캐폴딩하지 않습니다. 키 값은 데이터베이스에서 생성되며 변경할 수 없으며 사용자에게 표시할 의미 있는 값이 아니기 때문입니다. 과정 엔터티의 경우 스캐폴더는 특성이 사용자가 기본 키 값을 입력할 수 있어야 함을 DatabaseGeneratedOption.None
이해하기 때문에 필드에 대한 CourseID
텍스트 상자를 포함합니다. 그러나 숫자가 의미 있기 때문에 다른 보기에서 표시하려는 것을 이해하지 못하므로 수동으로 추가해야 합니다.
Views\Course\Edit.cshtml에서 제목 필드 앞에 강좌 번호 필드를 추가합니다. 기본 키이므로 표시되지만 변경할 수는 없습니다.
<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.LabelFor 도우미를 추가해도 사용자가 편집 페이지에서 저장을 클릭할 때 코스 번호가 게시된 데이터에 포함되지 않으므로 숨겨진 필드가 필요하지 않습니다.
Views\Course\Delete.cshtml and Views\Course\Details.cshtml에서 부서 이름 캡션을 "Name"에서 "Department"로 변경하고 제목 필드 앞에 강좌 번호 필드를 추가합니다.
<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을 입력합니다. |
타이틀 | 대수 입력 |
크레딧 | 4를 입력합니다. |
부서 | 수학을 선택합니다. |
만들기를 클릭합니다. 강좌 인덱스 페이지가 목록에 추가된 새 과정과 함께 표시됩니다. 인덱스 페이지 목록의 부서 이름은 관계가 올바르게 설정되었음을 표시하는 탐색 속성에서 제공됩니다.
편집 페이지를 실행합니다(강좌 인덱스 페이지를 표시하고 강좌에서 편집 클릭).
페이지에서 데이터를 변경하고 저장을 클릭합니다. 강좌 인덱스 페이지는 업데이트된 과정 데이터와 함께 표시됩니다.
강사에 사무실 추가 페이지
강사 레코드를 편집할 때 강사의 사무실 할당을 업데이트할 수 있습니다. Instructor
엔터티는 엔터티와 일대일 관계를 맺 OfficeAssignment
습니다. 즉, 다음과 같은 상황을 처리해야 합니다.
- 사용자가 사무실 할당을 지우고 원래 값이 있는 경우 엔터티를
OfficeAssignment
제거하고 삭제해야 합니다. - 사용자가 사무실 할당 값을 입력하고 원래 비어 있는 경우 새
OfficeAssignment
엔터티를 만들어야 합니다. - 사용자가 사무실 할당 값을 변경하는 경우 기존
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를 사용합니다.
코드는 다음을 수행합니다.
이제 서명이 메서드와
HttpGet
동일하기EditPost
때문에 메서드 이름을 변경합니다(ActionName
특성은 /Edit/URL이 계속 사용됨을 지정합니다).OfficeAssignment
탐색 속성에 대한 즉시 로드를 사용하여 데이터베이스에서 현재Instructor
엔터티를 가져옵니다. 이는 메서드에서 수행된 작업과HttpGet
Edit
동일합니다.모델 바인더의 값으로 검색된
Instructor
엔터티를 업데이트합니다. 사용된 TryUpdateModel 오버로드를 사용하면 포함할 속성을 나열할 수 있습니다. 이렇게 하면 두 번째 자습서에 설명된 대로 초과 게시를 방지합니다.if (TryUpdateModel(instructorToUpdate, "", new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
사무실 위치가 비어 있는 경우
OfficeAssignment
테이블의 관련된 행이 삭제되도록Instructor.OfficeAssignment
속성을 Null로 설정합니다.if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location)) { instructorToUpdate.OfficeAssignment = null; }
변경 내용을 데이터베이스에 저장합니다.
Views\Instructor\Edit.cshtml에서 고용 날짜 필드의 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>
페이지를 실행합니다(강사 탭을 선택한 다음 강사에서 편집 클릭). 사무실 위치를 변경하고 저장을 클릭합니다.
강사 페이지에 강좌 추가
강사는 강좌 수에 관계 없이 가르칠 수 있습니다. 이제 확인란 그룹을 사용하여 과정 과제를 변경하는 기능을 추가하여 강사 편집 페이지를 향상시킵니다.
엔터티와 Instructor
엔터티 간의 Course
관계는 다 대 다 관계이므로 조인 테이블에 있는 외래 키 속성에 직접 액세스할 수 없습니다. 대신 탐색 속성에서 엔터티를 Instructor.Courses
추가하고 제거합니다.
강사에게 할당된 강좌를 변경할 수 있도록 하는 UI는 확인란의 그룹입니다. 데이터베이스의 모든 강좌에 대한 확인란이 표시되고 강사에게 현재 할당되어 있는 것이 선택됩니다. 사용자는 확인란을 선택하거나 선택 취소하여 강좌 할당을 변경할 수 있습니다. 과정 수가 훨씬 많은 경우 뷰에서 데이터를 표시하는 다른 방법을 사용하려고 하지만 관계를 만들거나 삭제하기 위해 탐색 속성을 조작하는 동일한 방법을 사용합니다.
확인란의 목록에 대한 보기에 데이터를 제공하려면 보기 모델 클래스를 사용합니다. ViewModels 폴더에서 AssignedCourseData.cs 만들고 기존 코드를 다음 코드로 바꿉니다.
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();
if (instructor == null)
{
return HttpNotFound();
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
private void PopulateAssignedCourseData(Instructor instructor)
{
var allCourses = db.Courses;
var instructorCourses = new HashSet<int>(instructor.Courses.Select(c => c.CourseID));
var viewModel = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
viewModel.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
ViewBag.Courses = viewModel;
}
코드는 Courses
탐색 속성에 대해 즉시 로드를 추가하고 새 PopulateAssignedCourseData
메서드를 호출하여 AssignedCourseData
보기 모델 클래스를 사용하여 확인란 배열에 대한 정보를 제공합니다.
PopulateAssignedCourseData
메서드의 코드는 보기 모델 클래스를 사용하는 강좌의 목록을 로드하기 위해 모든 Course
엔터티를 통해 읽습니다. 각 강좌의 경우 코드는 강좌가 강사의 Courses
탐색 속성에 있는지 여부를 확인합니다. 강사에게 강좌가 할당되었는지 여부를 확인할 때 효율적인 조회를 만들기 위해 강사에게 할당된 과정은 HashSet 컬렉션에 배치됩니다. 이 Assigned
속성은 강사가 true
할당된 과정에 대해 설정됩니다. 보기는 이 속성을 사용하여 선택된 것으로 표시되어야 하는 확인란을 결정합니다. 마지막으로 목록이 속성의 뷰에 ViewBag
전달됩니다.
다음으로 사용자가 저장을 클릭할 때 실행되는 코드를 추가합니다. 엔터티의 EditPost
탐색 속성을 업데이트하는 새 메서드를 호출하는 다음 코드로 메서드를 Instructor
바꿉니다Courses
. 변경 내용은 강조 표시되어 있습니다.
[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
를 사용하고 포함 목록에 없기 때문에 TryUpdateModel을 호출하는 코드를 변경할 필요가 없습니다.
확인란을 선택하지 않은 경우 코드는 UpdateInstructorCourses
빈 컬렉션을 사용하여 Courses
탐색 속성을 초기화합니다.
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
그런 다음, 코드는 데이터베이스의 모든 강좌를 반복하고 현재 강사에게 할당된 것과 보기에서 선택되었던 것에 대해 각 강좌를 확인합니다. 효율적인 조회를 수행하기 위해 후자의 두 컬렉션은 HashSet
개체에 저장됩니다.
강좌에 대한 확인란이 선택됐지만 강좌가 Instructor.Courses
탐색 속성에 없는 경우 강좌는 탐색 속성의 컬렉션에 추가됩니다.
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
강좌에 대한 확인란이 선택되지 않았지만 강좌가 Instructor.Courses
탐색 속성에 있는 경우 강좌는 탐색 속성에서 제거됩니다.
else
{
if (instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Remove(course);
}
}
Views\Instructor\Edit.cshtml에서 필드 요소 바로 뒤 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
때 변경 내용을 확인할 수 있습니다. 따라서 해당 페이지의 테이블에 열을 추가해야 합니다. 이 경우 표시하려는 정보가 모델로 페이지에 전달하는 엔터티의 Instructor
탐색 속성에 Courses
이미 있으므로 개체를 사용할 ViewBag
필요가 없습니다.
Views\Instructor\Index.cshtml에서 다음 예제와 같이 Office 제목 바로 다음에 강좌 제목을 추가합니다.
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
그런 다음 사무실 위치 세부 정보 셀 바로 다음에 새 세부 정보 셀을 추가합니다.
<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>
강사 인 덱스 페이지를 실행하여 각 강사에게 할당된 과정을 확인합니다.
강사에서 편집을 클릭하여 편집 페이지를 확인합니다.
일부 과정 과제를 변경하고 저장을 클릭합니다. 변경 내용은 인덱스 페이지에 반영됩니다.
참고: 강사 과정 데이터를 편집하기 위해 여기에 적용된 접근 방식은 제한된 수의 과정이 있을 때 잘 작동합니다. 훨씬 큰 컬렉션의 경우 다른 UI 및 다른 업데이트 메서드가 필요합니다.
DeleteConfirmed 업데이트
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);
}
이 코드는 처음에 선택된 과정이 없다는 점을 제외하고 Edit 메서드에 대해 본 코드와 유사합니다. 이 메서드는 HttpGet
Create
선택한 과정이 있을 수 있기 때문이 아니라 뷰에서 루프에 대한 빈 컬렉션을 제공하기 위해 foreach
메서드를 호출 PopulateAssignedCourseData
합니다(그렇지 않으면 뷰 코드가 null 참조 예외를 throw).
HttpPost Create 메서드는 유효성 검사 오류를 확인하고 새 강사를 데이터베이스에 추가하는 템플릿 코드 앞에 선택한 각 과정을 Courses 탐색 속성에 추가합니다. 모델 오류가 있는 경우에도 코스가 추가되므로 모델 오류가 있는 경우(예: 사용자가 잘못된 날짜를 키 지정한 경우) 오류 메시지와 함께 페이지가 다시 표시될 때 선택한 모든 강좌가 자동으로 복원됩니다.
Courses
탐색 속성에 강좌를 추가할 수 있도록 빈 컬렉션으로 속성을 초기화해야 합니다.
instructor.Courses = new List<Course>();
컨트롤러 코드에서 이 작업을 수행하는 대안으로 다음 예제와 같이 존재하지 않는 경우 자동으로 컬렉션을 만들도록 getter 속성을 변경하여 강사 모델에서 해당 작업을 수행할 수 있습니다.
private ICollection<Course> _courses;
public virtual ICollection<Course> Courses
{
get
{
return _courses ?? (_courses = new List<Course>());
}
set
{
_courses = value;
}
}
이러한 방식으로 Courses
속성을 수정하는 경우 컨트롤러에서 명시적 속성 초기화 코드를 제거할 수 있습니다.
Views\Instructor\Create.cshtml에서 채용 날짜 필드 뒤와 제출 단추 앞에 사무실 위치 텍스트 상자와 강좌 확인란을 추가합니다.
<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 추가됨
- 강사 페이지에 강좌 추가
- DeleteConfirmed 업데이트됨
- 만들기 페이지에 사무실 위치 및 과정 추가
다음 문서로 이동하여 비동기 프로그래밍 모델을 구현하는 방법을 알아봅니다.