다음을 통해 공유


7부. ASP.NET Core에서 EF Core를 사용한 Razor Pages - 관련 데이터 업데이트

작성자: Tom Dykstra, Jon P SmithRick Anderson

Contoso University 웹앱은 EF Core 및 Visual Studio를 사용하여 Razor Pages 웹앱을 만드는 방법을 보여줍니다. 자습서 시리즈에 대한 정보는 첫 번째 자습서를 참조합니다.

해결할 수 없는 문제가 발생한 경우 완성된 앱을 다운로드하고 자습서를 따라 만든 코드와 해당 코드를 비교합니다.

이 자습서에서는 관련된 데이터를 업데이트하는 방법을 보여 줍니다. 다음 그림은 완료된 페이지의 일부를 보여 줍니다.

Course Edit pageInstructor Edit page

과정 만들기 및 편집 페이지 업데이트

과정 만들기 및 편집 페이지의 스캐폴드된 코드에는 DepartmentIDint를 표시하는 부서 드롭다운 목록이 있습니다. 드롭다운에는 부서 이름이 표시되므로 두 페이지에 모두 부서 이름 목록이 필요합니다. 이 목록을 제공하려면 만들기 및 편집 페이지에 기본 클래스를 사용합니다.

과정 만들기 및 편집에 대한 기본 클래스 만들기

다음 코드를 사용하여 Pages/Courses/DepartmentNamePageModel.cs 파일을 만듭니다.

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
    public class DepartmentNamePageModel : PageModel
    {
        public SelectList DepartmentNameSL { get; set; }

        public void PopulateDepartmentsDropDownList(SchoolContext _context,
            object selectedDepartment = null)
        {
            var departmentsQuery = from d in _context.Departments
                                   orderby d.Name // Sort by name.
                                   select d;

            DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),
                nameof(Department.DepartmentID),
                nameof(Department.Name),
                selectedDepartment);
        }
    }
}

앞의 코드는 부서 이름 목록을 포함하는 메서드를 만듭니다 SelectList . selectedDepartment가 지정된 경우 해당 부서는 SelectList에서 선택됩니다.

만들기 및 편집 페이지 모델 클래스는 DepartmentNamePageModel에서 파생됩니다.

과정 만들기 페이지 모델 업데이트

과정이 부서에 할당됩니다. 만들기 및 편집 페이지의 기본 클래스는 부서를 선택하기 위한 SelectList를 제공합니다. SelectList를 사용하는 드롭다운 목록은 Course.DepartmentID FK(외래 키) 속성을 설정합니다. EF Core는 Course.DepartmentID FK를 사용하여 Department 탐색 속성을 로드합니다.

Create course

다음 코드를 사용하여 Pages/Courses/Create.cshtml.cs을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class CreateModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            PopulateDepartmentsDropDownList(_context);
            return Page();
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            var emptyCourse = new Course();

            if (await TryUpdateModelAsync<Course>(
                 emptyCourse,
                 "course",   // Prefix for form value.
                 s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
            {
                _context.Courses.Add(emptyCourse);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID);
            return Page();
        }
      }
}

영어 이외의 언어로 번역된 코드 주석을 보려면 이 GitHub 토론 이슈에서 알려주세요.

앞의 코드가 하는 역할은 다음과 같습니다.

  • DepartmentNamePageModel에서 파생됩니다.
  • 초과 게시를 방지하도록 TryUpdateModelAsync를 사용합니다.
  • ViewData["DepartmentID"]을 제거합니다. DepartmentNameSLSelectList는 강력한 형식의 모델이며 Razor 페이지에서 사용됩니다. 강력한 형식의 모델은 약한 형식보다 선호됩니다. 자세한 내용은 약한 형식의 데이터(ViewData 및 ViewBag)를 참조하세요.

과정 만들기 Razor 페이지 업데이트

다음 코드를 사용하여 Pages/Courses/Create.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
    ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <input asp-for="Course.CourseID" class="form-control" />
                <span asp-validation-for="Course.CourseID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control"
                        asp-items="@Model.DepartmentNameSL">
                    <option value="">-- Select Department --</option>
                </select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>
<div>
    <a asp-page="Index">Back to List</a>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

위의 코드로 다음이 변경됩니다.

  • 캡션을 DepartmentID에서 Department로 변경합니다.
  • "ViewBag.DepartmentID"DepartmentNameSL로 바꿉니다(기본 클래스에서).
  • "부서 선택" 옵션을 추가합니다. 이렇게 변경하면 아직 부서가 선택되지 않은 경우 첫 번째 부서가 아닌 “부서 선택”이 드롭다운에 렌더링됩니다.
  • 부서가 선택되지 않은 경우 유효성 검사 메시지를 추가합니다.

Razor 페이지는 Select 태그 도우미를 사용합니다.

<div class="form-group">
    <label asp-for="Course.Department" class="control-label"></label>
    <select asp-for="Course.DepartmentID" class="form-control"
            asp-items="@Model.DepartmentNameSL">
        <option value="">-- Select Department --</option>
    </select>
    <span asp-validation-for="Course.DepartmentID" class="text-danger" />
</div>

Create 페이지를 테스트합니다. 만들기 페이지는 부서 ID보다는 부서 이름을 표시합니다.

과정 편집 페이지 모델 업데이트

다음 코드를 사용하여 Pages/Courses/Edit.cshtml.cs을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class EditModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Course = await _context.Courses
                .Include(c => c.Department).FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }

            // Select current DepartmentID.
            PopulateDepartmentsDropDownList(_context, Course.DepartmentID);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var courseToUpdate = await _context.Courses.FindAsync(id);

            if (courseToUpdate == null)
            {
                return NotFound();
            }

            if (await TryUpdateModelAsync<Course>(
                 courseToUpdate,
                 "course",   // Prefix for form value.
                   c => c.Credits, c => c.DepartmentID, c => c.Title))
            {
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID);
            return Page();
        }       
    }
}

변경 내용은 만들기 페이지 모델에서 만든 것과 비슷합니다. 앞의 코드에서 PopulateDepartmentsDropDownList는 드롭다운 목록에서 부서를 선택하는 부서 ID를 전달합니다.

과정 편집 Razor 페이지 업데이트

다음 코드를 사용하여 Pages/Courses/Edit.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Courses.EditModel

@{
    ViewData["Title"] = "Edit";
}

<h2>Edit</h2>

<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Course.CourseID" />
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <div>@Html.DisplayFor(model => model.Course.CourseID)</div>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control" 
                        asp-items="@Model.DepartmentNameSL"></select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

위의 코드로 다음이 변경됩니다.

  • 강좌 ID를 표시합니다. 일반적으로 엔터티의 PK(기본 키)는 표시되지 않습니다. PK는 일반적으로 사용자에게 아무런 의미가 없습니다. 이 경우 PK는 강좌 번호입니다.
  • 부서 드롭다운의 캡션을 DepartmentID에서 Department로 변경합니다.
  • "ViewBag.DepartmentID"을 기본 클래스에 있는 DepartmentNameSL로 대체합니다.

페이지는 강좌 번호에 대한 숨겨진 필드(<input type="hidden">)를 포함합니다. asp-for="Course.CourseID"<label> 태그 도우미를 추가하는 것은 숨겨진 필드에 대한 필요성을 제거하지 않습니다. <input type="hidden">은 사용자가 저장을 선택할 때 게시된 데이터에 포함되도록 과정 코드에 필요합니다.

과정 페이지 모델 업데이트

AsNoTracking 는 추적이 필요하지 않을 때 성능을 향상시킬 수 있습니다.

OnGetAsync 메서드에 AsNoTracking을 추가하여 Pages/Courses/Delete.cshtml.csPages/Courses/Details.cshtml.cs를 업데이트합니다.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Course = await _context.Courses
        .AsNoTracking()
        .Include(c => c.Department)
        .FirstOrDefaultAsync(m => m.CourseID == id);

    if (Course == null)
    {
        return NotFound();
    }
    return Page();
}

과정 Razor Pages 업데이트

다음 코드를 사용하여 Pages/Courses/Delete.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Courses.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Course</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.CourseID)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Department.Name)
        </dd>
    </dl>
    
    <form method="post">
        <input type="hidden" asp-for="Course.CourseID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

세부 정보 페이지에 동일한 변경 내용을 만듭니다.

@page
@model ContosoUniversity.Pages.Courses.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
    <h4>Course</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.CourseID)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Department.Name)
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Course.CourseID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

강좌 페이지 테스트

만들기, 편집, 세부 정보 및 삭제 페이지를 테스트합니다.

강사 만들기 및 편집 페이지 업데이트

강사는 강좌 수에 관계 없이 가르칠 수 있습니다. 다음 이미지는 과정 확인란 배열이 포함된 강사 편집 페이지를 보여 줍니다.

Instructor Edit page with courses

확인란을 선택하면 강사에게 할당된 과정에 변경 내용이 적용됩니다. 데이터베이스의 모든 과정에 해당하는 확인란이 표시됩니다. 강사에게 할당된 과정이 선택됩니다. 사용자는 확인란을 선택하거나 선택 취소하여 과정 할당을 변경할 수 있습니다. 과정 수가 훨씬 더 많은 경우 다른 UI가 더 효과적일 수 있습니다. 그러나 여기에 표시된 다대다 관계를 관리하는 메서드는 변경되지 않습니다. 관계를 만들거나 삭제하려면 조인 엔터티를 조작합니다.

할당된 과정 데이터에 대한 클래스 만들기

다음 코드를 사용하여 만듭니 Models/SchoolViewModels/AssignedCourseData.cs 다.

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

AssignedCourseData 클래스는 강사에게 할당된 과정에 대한 확인란을 만들 데이터를 포함합니다.

강사 페이지 모델 기본 클래스 만들기

Pages/Instructors/InstructorCoursesPageModel.cs 기본 클래스를 만듭니다.

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
    public class InstructorCoursesPageModel : PageModel
    {
        public List<AssignedCourseData> AssignedCourseDataList;

        public void PopulateAssignedCourseData(SchoolContext context,
                                               Instructor instructor)
        {
            var allCourses = context.Courses;
            var instructorCourses = new HashSet<int>(
                instructor.Courses.Select(c => c.CourseID));
            AssignedCourseDataList = new List<AssignedCourseData>();
            foreach (var course in allCourses)
            {
                AssignedCourseDataList.Add(new AssignedCourseData
                {
                    CourseID = course.CourseID,
                    Title = course.Title,
                    Assigned = instructorCourses.Contains(course.CourseID)
                });
            }
        }
    }
}

InstructorCoursesPageModel은 편집 및 만들기 페이지 모델에 사용하는 기본 클래스입니다. PopulateAssignedCourseDataAssignedCourseDataList를 채우도록 모든 Course 엔터티를 읽습니다. 각 강좌의 경우 코드는 CourseID, 제목 및 강사가 강좌에 할당되었는지 여부를 설정합니다. HashSet는 효율적인 조회를 위해 사용됩니다.

사무실 위치 처리

편집 페이지에서 처리해야 하는 또 다른 관계는 강사 엔터티와 OfficeAssignment 엔터티 간의 일대영/일 관계입니다. 강사 편집 코드는 다음 시나리오를 처리해야 합니다.

  • 사용자가 사무실 할당을 해제하는 경우 OfficeAssignment 엔터티를 삭제합니다.
  • 사용자가 사무실 할당을 입력하고 비어 있던 경우 새 OfficeAssignment 엔터티를 만듭니다.
  • 사용자가 사무실 할당을 변경하는 경우 OfficeAssignment 엔터티를 업데이트합니다.

강사 편집 페이지 모델 업데이트

다음 코드를 사용하여 Pages/Instructors/Edit.cshtml.cs을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class EditModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor = await _context.Instructors
                .Include(i => i.OfficeAssignment)
                .Include(i => i.Courses)
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            PopulateAssignedCourseData(_context, Instructor);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id, string[] selectedCourses)
        {
            if (id == null)
            {
                return NotFound();
            }

            var instructorToUpdate = await _context.Instructors
                .Include(i => i.OfficeAssignment)
                .Include(i => i.Courses)
                .FirstOrDefaultAsync(s => s.ID == id);

            if (instructorToUpdate == null)
            {
                return NotFound();
            }

            if (await TryUpdateModelAsync<Instructor>(
                instructorToUpdate,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                if (String.IsNullOrWhiteSpace(
                    instructorToUpdate.OfficeAssignment?.Location))
                {
                    instructorToUpdate.OfficeAssignment = null;
                }
                UpdateInstructorCourses(selectedCourses, instructorToUpdate);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            UpdateInstructorCourses(selectedCourses, instructorToUpdate);
            PopulateAssignedCourseData(_context, instructorToUpdate);
            return Page();
        }

        public 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 _context.Courses)
            {
                if (selectedCoursesHS.Contains(course.CourseID.ToString()))
                {
                    if (!instructorCourses.Contains(course.CourseID))
                    {
                        instructorToUpdate.Courses.Add(course);
                    }
                }
                else
                {
                    if (instructorCourses.Contains(course.CourseID))
                    {
                        var courseToRemove = instructorToUpdate.Courses.Single(
                                                        c => c.CourseID == course.CourseID);
                        instructorToUpdate.Courses.Remove(courseToRemove);
                    }
                }
            }
        }
    }
}

앞의 코드가 하는 역할은 다음과 같습니다.

  • OfficeAssignmentCourses 탐색 속성에 대한 즉시 로드를 사용하여 데이터베이스에서 현재 Instructor 엔터티를 가져옵니다.
  • 모델 바인더의 값으로 검색된 Instructor 엔터티를 업데이트합니다. TryUpdateModelAsync초과 게시를 방지합니다.
  • 사무실 위치가 비어 있는 경우 Instructor.OfficeAssignment를 Null로 설정합니다. Instructor.OfficeAssignment가 Null인 경우 OfficeAssignment 테이블의 관련된 행이 삭제됩니다.
  • OnGetAsync에서 PopulateAssignedCourseData를 호출하여 AssignedCourseData 뷰 모델 클래스를 통해 확인란에 대한 정보를 제공합니다.
  • OnPostAsync에서 UpdateInstructorCourses를 호출하여 확인란의 정보를 편집 중인 Instructor 엔터티에 적용합니다.
  • TryUpdateModelAsync이 실패하는 경우 OnPostAsync에서 PopulateAssignedCourseDataUpdateInstructorCourses를 호출합니다. 이 메서드 호출은 오류 메시지와 함께 다시 표시될 때 페이지에 입력된 할당된 과정 데이터를 복원합니다.

Razor 페이지에는 과정 엔터티의 컬렉션이 없으므로 모델 바인더는 Courses 탐색 속성을 자동으로 업데이트할 수 없습니다. Courses 탐색 속성을 업데이트하는 데 모델 바인더를 사용하는 대신 새 UpdateInstructorCourses 메서드에서 해당 작업을 수행합니다. 따라서 모델 바인딩에서 Courses 속성을 제외해야 합니다. 선언된 속성을 포함하는 오버로드를 사용 중이고 Courses가 포함 목록에 있지 않으므로 TryUpdateModelAsync을 호출하는 코드를 변경할 필요가 없습니다.

확인란이 선택되지 않은 경우 UpdateInstructorCourses의 코드는 빈 컬렉션으로 instructorToUpdate.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))
    {
        var courseToRemove = instructorToUpdate.Courses.Single(
                                        c => c.CourseID == course.CourseID);
        instructorToUpdate.Courses.Remove(courseToRemove);
    }
}

강사 편집 Razor 페이지 업데이트

다음 코드를 사용하여 Pages/Instructors/Edit.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

위의 코드는 세 개의 열이 있는 HTML 테이블을 만듭니다. 각 열에는 확인란과 과정 번호 및 제목을 포함하는 캡션이 있습니다. 모든 확인란의 이름은 동일합니다("selectedCourses"). 동일한 이름을 사용하면 모델 바인더에 그룹으로 취급하도록 알립니다. 각 확인란의 값 특성은 CourseID로 설정됩니다. 페이지가 게시되면 모델 바인더는 선택된 확인란만의 CourseID 값으로 구성된 배열을 전달합니다.

확인란이 처음 렌더링되는 경우 강사에게 할당된 과정이 선택됩니다.

참고: 강사 강좌 데이터를 편집하기 위해 여기에 적용되는 방법은 제한된 수의 강좌가 있는 경우에 잘 작동합니다. 훨씬 큰 컬렉션의 경우 다른 UI 및 다른 업데이트 메서드는 더욱 사용 가능하며 효율적입니다.

앱을 실행하고 업데이트된 강사 편집 페이지를 테스트합니다. 일부 강좌 할당을 변경합니다. 변경 내용은 인덱스 페이지에 반영됩니다.

강사 만들기 페이지 업데이트

편집 페이지와 유사한 코드를 사용하여 강사 만들기 페이지 모델을 업데이트합니다.

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class CreateModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;
        private readonly ILogger<InstructorCoursesPageModel> _logger;

        public CreateModel(SchoolContext context,
                          ILogger<InstructorCoursesPageModel> logger)
        {
            _context = context;
            _logger = logger;
        }

        public IActionResult OnGet()
        {
            var instructor = new Instructor();
            instructor.Courses = new List<Course>();

            // Provides an empty collection for the foreach loop
            // foreach (var course in Model.AssignedCourseDataList)
            // in the Create Razor page.
            PopulateAssignedCourseData(_context, instructor);
            return Page();
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
        {
            var newInstructor = new Instructor();

            if (selectedCourses.Length > 0)
            {
                newInstructor.Courses = new List<Course>();
                // Load collection with one DB call.
                _context.Courses.Load();
            }

            // Add selected Courses courses to the new instructor.
            foreach (var course in selectedCourses)
            {
                var foundCourse = await _context.Courses.FindAsync(int.Parse(course));
                if (foundCourse != null)
                {
                    newInstructor.Courses.Add(foundCourse);
                }
                else
                {
                    _logger.LogWarning("Course {course} not found", course);
                }
            }

            try
            {
                if (await TryUpdateModelAsync<Instructor>(
                                newInstructor,
                                "Instructor",
                                i => i.FirstMidName, i => i.LastName,
                                i => i.HireDate, i => i.OfficeAssignment))
                {
                    _context.Instructors.Add(newInstructor);
                    await _context.SaveChangesAsync();
                    return RedirectToPage("./Index");
                }
                return RedirectToPage("./Index");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }

            PopulateAssignedCourseData(_context, newInstructor);
            return Page();
        }
    }
}

앞의 코드가 하는 역할은 다음과 같습니다.

  • 경고 및 오류 메시지에 대한 로깅을 추가합니다.

  • Load을 호출하여 한 데이터베이스 호출의 모든 과정을 페치합니다. 작은 컬렉션의 경우, 최적화를 위해 FindAsync을 사용하는 것이 좋습니다. FindAsync은 데이터베이스에 대한 요청 없이 추적된 엔터티를 반환합니다.

    public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
    {
        var newInstructor = new Instructor();
    
        if (selectedCourses.Length > 0)
        {
            newInstructor.Courses = new List<Course>();
            // Load collection with one DB call.
            _context.Courses.Load();
        }
    
        // Add selected Courses courses to the new instructor.
        foreach (var course in selectedCourses)
        {
            var foundCourse = await _context.Courses.FindAsync(int.Parse(course));
            if (foundCourse != null)
            {
                newInstructor.Courses.Add(foundCourse);
            }
            else
            {
                _logger.LogWarning("Course {course} not found", course);
            }
        }
    
        try
        {
            if (await TryUpdateModelAsync<Instructor>(
                            newInstructor,
                            "Instructor",
                            i => i.FirstMidName, i => i.LastName,
                            i => i.HireDate, i => i.OfficeAssignment))
            {
                _context.Instructors.Add(newInstructor);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            return RedirectToPage("./Index");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
        }
    
        PopulateAssignedCourseData(_context, newInstructor);
        return Page();
    }
    
  • _context.Instructors.Add(newInstructor)은 조인 테이블을 명시적으로 매핑하지 않고 다대다 관계를 사용하여 새로운 Instructor를 만들어냅니다. 다대다는 EF 5.0에 추가되었습니다.

강사 만들기 페이지를 테스트합니다.

편집 페이지와 유사한 코드를 사용하여 강사 만들기 페이지 Razor 을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Instructors.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

강사 삭제 페이지 업데이트

다음 코드를 사용하여 Pages/Instructors/Delete.cshtml.cs을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor = await _context.Instructors.FirstOrDefaultAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor instructor = await _context.Instructors
                .Include(i => i.Courses)
                .SingleAsync(i => i.ID == id);

            if (instructor == null)
            {
                return RedirectToPage("./Index");
            }

            var departments = await _context.Departments
                .Where(d => d.InstructorID == id)
                .ToListAsync();
            departments.ForEach(d => d.InstructorID = null);

            _context.Instructors.Remove(instructor);

            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }
    }
}

위의 코드로 다음이 변경됩니다.

  • Courses 탐색 속성에 대해 즉시 로드를 사용합니다. Courses는 포함되어야 합니다. 또는 강사가 삭제될 때 삭제되지 않습니다. 읽을 필요가 없도록 하려면 데이터베이스에 계단식 삭제를 구성합니다.

  • 삭제될 강사가 부서의 관리자로 할당된 경우 해당 부서에서 강사 할당을 제거합니다.

앱을 실행하고 삭제 페이지를 테스트합니다.

다음 단계

이 자습서에서는 관련된 데이터를 업데이트하는 방법을 보여 줍니다. 다음 그림은 완료된 페이지의 일부를 보여 줍니다.

Course Edit pageInstructor Edit page

과정 만들기 및 편집 페이지 업데이트

과정 만들기 및 편집 페이지의 스캐폴드된 코드에는 부서 ID(정수)를 표시하는 부서 드롭다운 목록이 있습니다. 드롭다운에는 부서 이름이 표시되므로 두 페이지에 모두 부서 이름 목록이 필요합니다. 이 목록을 제공하려면 만들기 및 편집 페이지에 기본 클래스를 사용합니다.

과정 만들기 및 편집에 대한 기본 클래스 만들기

다음 코드를 사용하여 Pages/Courses/DepartmentNamePageModel.cs 파일을 만듭니다.

using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
    public class DepartmentNamePageModel : PageModel
    {
        public SelectList DepartmentNameSL { get; set; }

        public void PopulateDepartmentsDropDownList(SchoolContext _context,
            object selectedDepartment = null)
        {
            var departmentsQuery = from d in _context.Departments
                                   orderby d.Name // Sort by name.
                                   select d;

            DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),
                        "DepartmentID", "Name", selectedDepartment);
        }
    }
}

앞의 코드는 부서 이름 목록을 포함하는 메서드를 만듭니다 SelectList . selectedDepartment가 지정된 경우 해당 부서는 SelectList에서 선택됩니다.

만들기 및 편집 페이지 모델 클래스는 DepartmentNamePageModel에서 파생됩니다.

과정 만들기 페이지 모델 업데이트

과정이 부서에 할당됩니다. 만들기 및 편집 페이지의 기본 클래스는 부서를 선택하기 위한 SelectList를 제공합니다. SelectList를 사용하는 드롭다운 목록은 Course.DepartmentID FK(외래 키) 속성을 설정합니다. EF Core는 Course.DepartmentID FK를 사용하여 Department 탐색 속성을 로드합니다.

Create course

다음 코드를 사용하여 Pages/Courses/Create.cshtml.cs을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class CreateModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            PopulateDepartmentsDropDownList(_context);
            return Page();
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            var emptyCourse = new Course();

            if (await TryUpdateModelAsync<Course>(
                 emptyCourse,
                 "course",   // Prefix for form value.
                 s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
            {
                _context.Courses.Add(emptyCourse);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID);
            return Page();
        }
      }
}

영어 이외의 언어로 번역된 코드 주석을 보려면 이 GitHub 토론 이슈에서 알려주세요.

앞의 코드가 하는 역할은 다음과 같습니다.

  • DepartmentNamePageModel에서 파생됩니다.
  • 초과 게시를 방지하도록 TryUpdateModelAsync를 사용합니다.
  • ViewData["DepartmentID"]을 제거합니다. 기본 클래스의 DepartmentNameSL은 강력한 형식의 모델이며 Razor 페이지에서 사용됩니다. 강력한 형식의 모델은 약한 형식보다 선호됩니다. 자세한 내용은 약한 형식의 데이터(ViewData 및 ViewBag)를 참조하세요.

과정 만들기 Razor 페이지 업데이트

다음 코드를 사용하여 Pages/Courses/Create.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
    ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <input asp-for="Course.CourseID" class="form-control" />
                <span asp-validation-for="Course.CourseID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control"
                        asp-items="@Model.DepartmentNameSL">
                    <option value="">-- Select Department --</option>
                </select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>
<div>
    <a asp-page="Index">Back to List</a>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

위의 코드로 다음이 변경됩니다.

  • 캡션을 DepartmentID에서 Department로 변경합니다.
  • "ViewBag.DepartmentID"DepartmentNameSL로 바꿉니다(기본 클래스에서).
  • "부서 선택" 옵션을 추가합니다. 이렇게 변경하면 아직 부서가 선택되지 않은 경우 첫 번째 부서가 아닌 “부서 선택”이 드롭다운에 렌더링됩니다.
  • 부서가 선택되지 않은 경우 유효성 검사 메시지를 추가합니다.

Razor 페이지는 Select 태그 도우미를 사용합니다.

<div class="form-group">
    <label asp-for="Course.Department" class="control-label"></label>
    <select asp-for="Course.DepartmentID" class="form-control"
            asp-items="@Model.DepartmentNameSL">
        <option value="">-- Select Department --</option>
    </select>
    <span asp-validation-for="Course.DepartmentID" class="text-danger" />
</div>

Create 페이지를 테스트합니다. 만들기 페이지는 부서 ID보다는 부서 이름을 표시합니다.

과정 편집 페이지 모델 업데이트

다음 코드를 사용하여 Pages/Courses/Edit.cshtml.cs을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class EditModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Course = await _context.Courses
                .Include(c => c.Department).FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }

            // Select current DepartmentID.
            PopulateDepartmentsDropDownList(_context, Course.DepartmentID);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var courseToUpdate = await _context.Courses.FindAsync(id);

            if (courseToUpdate == null)
            {
                return NotFound();
            }

            if (await TryUpdateModelAsync<Course>(
                 courseToUpdate,
                 "course",   // Prefix for form value.
                   c => c.Credits, c => c.DepartmentID, c => c.Title))
            {
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID);
            return Page();
        }       
    }
}

변경 내용은 만들기 페이지 모델에서 만든 것과 비슷합니다. 앞의 코드에서 PopulateDepartmentsDropDownList는 드롭다운 목록에서 부서를 선택하는 부서 ID를 전달합니다.

과정 편집 Razor 페이지 업데이트

다음 코드를 사용하여 Pages/Courses/Edit.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Courses.EditModel

@{
    ViewData["Title"] = "Edit";
}

<h2>Edit</h2>

<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Course.CourseID" />
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <div>@Html.DisplayFor(model => model.Course.CourseID)</div>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control" 
                        asp-items="@Model.DepartmentNameSL"></select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

위의 코드로 다음이 변경됩니다.

  • 강좌 ID를 표시합니다. 일반적으로 엔터티의 PK(기본 키)는 표시되지 않습니다. PK는 일반적으로 사용자에게 아무런 의미가 없습니다. 이 경우 PK는 강좌 번호입니다.
  • 부서 드롭다운의 캡션을 DepartmentID에서 Department로 변경합니다.
  • "ViewBag.DepartmentID"DepartmentNameSL로 바꿉니다(기본 클래스에서).

페이지는 강좌 번호에 대한 숨겨진 필드(<input type="hidden">)를 포함합니다. asp-for="Course.CourseID"<label> 태그 도우미를 추가하는 것은 숨겨진 필드에 대한 필요성을 제거하지 않습니다. <input type="hidden">은 사용자가 저장을 클릭할 때 게시된 데이터에 포함되도록 강좌 번호에 필요합니다.

과정 세부 정보 및 삭제 페이지 업데이트

AsNoTracking 는 추적이 필요하지 않을 때 성능을 향상시킬 수 있습니다.

과정 페이지 모델 업데이트

다음 코드로 Pages/Courses/Delete.cshtml.cs를 업데이트하여 AsNoTracking을 추가합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Course = await _context.Courses
                .AsNoTracking()
                .Include(c => c.Department)
                .FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Course = await _context.Courses.FindAsync(id);

            if (Course != null)
            {
                _context.Courses.Remove(Course);
                await _context.SaveChangesAsync();
            }

            return RedirectToPage("./Index");
        }
    }
}

파일에서 동일한 변경을 수행합니다.Pages/Courses/Details.cshtml.cs

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class DetailsModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DetailsModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public Course Course { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Course = await _context.Courses
                 .AsNoTracking()
                 .Include(c => c.Department)
                 .FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }
            return Page();
        }
    }
}

과정 Razor Pages 업데이트

다음 코드를 사용하여 Pages/Courses/Delete.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Courses.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Course</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.CourseID)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Department.Name)
        </dd>
    </dl>
    
    <form method="post">
        <input type="hidden" asp-for="Course.CourseID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

세부 정보 페이지에 동일한 변경 내용을 만듭니다.

@page
@model ContosoUniversity.Pages.Courses.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
    <h4>Course</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.CourseID)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Department.Name)
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Course.CourseID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

강좌 페이지 테스트

만들기, 편집, 세부 정보 및 삭제 페이지를 테스트합니다.

강사 만들기 및 편집 페이지 업데이트

강사는 강좌 수에 관계 없이 가르칠 수 있습니다. 다음 이미지는 과정 확인란 배열이 포함된 강사 편집 페이지를 보여 줍니다.

Instructor Edit page with courses

확인란을 선택하면 강사에게 할당된 과정에 변경 내용이 적용됩니다. 데이터베이스의 모든 과정에 해당하는 확인란이 표시됩니다. 강사에게 할당된 과정이 선택됩니다. 사용자는 확인란을 선택하거나 선택 취소하여 과정 할당을 변경할 수 있습니다. 과정 수가 훨씬 더 많은 경우 다른 UI가 더 효과적일 수 있습니다. 그러나 여기에 표시된 다대다 관계를 관리하는 메서드는 변경되지 않습니다. 관계를 만들거나 삭제하려면 조인 엔터티를 조작합니다.

할당된 과정 데이터에 대한 클래스 만들기

다음 코드를 사용하여 만듭니 Models/SchoolViewModels/AssignedCourseData.cs 다.

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

AssignedCourseData 클래스는 강사에게 할당된 과정에 대한 확인란을 만들 데이터를 포함합니다.

강사 페이지 모델 기본 클래스 만들기

Pages/Instructors/InstructorCoursesPageModel.cs 기본 클래스를 만듭니다.

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
    public class InstructorCoursesPageModel : PageModel
    {

        public List<AssignedCourseData> AssignedCourseDataList;

        public void PopulateAssignedCourseData(SchoolContext context, 
                                               Instructor instructor)
        {
            var allCourses = context.Courses;
            var instructorCourses = new HashSet<int>(
                instructor.CourseAssignments.Select(c => c.CourseID));
            AssignedCourseDataList = new List<AssignedCourseData>();
            foreach (var course in allCourses)
            {
                AssignedCourseDataList.Add(new AssignedCourseData
                {
                    CourseID = course.CourseID,
                    Title = course.Title,
                    Assigned = instructorCourses.Contains(course.CourseID)
                });
            }
        }

        public void UpdateInstructorCourses(SchoolContext context, 
            string[] selectedCourses, Instructor instructorToUpdate)
        {
            if (selectedCourses == null)
            {
                instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
                return;
            }

            var selectedCoursesHS = new HashSet<string>(selectedCourses);
            var instructorCourses = new HashSet<int>
                (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
            foreach (var course in context.Courses)
            {
                if (selectedCoursesHS.Contains(course.CourseID.ToString()))
                {
                    if (!instructorCourses.Contains(course.CourseID))
                    {
                        instructorToUpdate.CourseAssignments.Add(
                            new CourseAssignment
                            {
                                InstructorID = instructorToUpdate.ID,
                                CourseID = course.CourseID
                            });
                    }
                }
                else
                {
                    if (instructorCourses.Contains(course.CourseID))
                    {
                        CourseAssignment courseToRemove
                            = instructorToUpdate
                                .CourseAssignments
                                .SingleOrDefault(i => i.CourseID == course.CourseID);
                        context.Remove(courseToRemove);
                    }
                }
            }
        }
    }
}

InstructorCoursesPageModel은 편집 및 만들기 페이지 모델에 사용하는 기본 클래스입니다. PopulateAssignedCourseDataAssignedCourseDataList를 채우도록 모든 Course 엔터티를 읽습니다. 각 강좌의 경우 코드는 CourseID, 제목 및 강사가 강좌에 할당되었는지 여부를 설정합니다. HashSet는 효율적인 조회를 위해 사용됩니다.

Razor 페이지에는 과정 엔터티의 컬렉션이 없으므로 모델 바인더는 CourseAssignments 탐색 속성을 자동으로 업데이트할 수 없습니다. CourseAssignments 탐색 속성을 업데이트하는 데 모델 바인더를 사용하는 대신 새 UpdateInstructorCourses 메서드에서 해당 작업을 수행합니다. 따라서 모델 바인딩에서 CourseAssignments 속성을 제외해야 합니다. 선언된 속성을 포함하는 오버로드를 사용 중이고 CourseAssignments가 포함 목록에 있지 않으므로 TryUpdateModel을 호출하는 코드를 변경할 필요가 없습니다.

확인란이 선택되지 않은 경우 UpdateInstructorCourses의 코드는 빈 컬렉션으로 CourseAssignments 탐색 속성을 초기화하고 다음을 반환합니다.

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

그런 다음, 코드는 데이터베이스의 모든 과정을 반복하고 현재 강사에게 할당된 것과 페이지에서 선택되었던 것에 대해 각 과정을 확인합니다. 효율적인 조회를 수행하기 위해 후자의 두 컬렉션은 HashSet 개체에 저장됩니다.

강좌에 대한 확인란이 선택됐지만 강좌가 Instructor.CourseAssignments 탐색 속성에 없는 경우 강좌는 탐색 속성의 컬렉션에 추가됩니다.

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
    if (!instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.CourseAssignments.Add(
            new CourseAssignment
            {
                InstructorID = instructorToUpdate.ID,
                CourseID = course.CourseID
            });
    }
}

강좌에 대한 확인란이 선택되지 않았지만 강좌가 Instructor.CourseAssignments 탐색 속성에 있는 경우 강좌는 탐색 속성에서 제거됩니다.

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        CourseAssignment courseToRemove
            = instructorToUpdate
                .CourseAssignments
                .SingleOrDefault(i => i.CourseID == course.CourseID);
        context.Remove(courseToRemove);
    }
}

사무실 위치 처리

편집 페이지에서 처리해야 하는 또 다른 관계는 강사 엔터티와 OfficeAssignment 엔터티 간의 일대영/일 관계입니다. 강사 편집 코드는 다음 시나리오를 처리해야 합니다.

  • 사용자가 사무실 할당을 해제하는 경우 OfficeAssignment 엔터티를 삭제합니다.
  • 사용자가 사무실 할당을 입력하고 비어 있던 경우 새 OfficeAssignment 엔터티를 만듭니다.
  • 사용자가 사무실 할당을 변경하는 경우 OfficeAssignment 엔터티를 업데이트합니다.

강사 편집 페이지 모델 업데이트

다음 코드를 사용하여 Pages/Instructors/Edit.cshtml.cs을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class EditModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor = await _context.Instructors
                .Include(i => i.OfficeAssignment)
                .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            PopulateAssignedCourseData(_context, Instructor);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id, string[] selectedCourses)
        {
            if (id == null)
            {
                return NotFound();
            }

            var instructorToUpdate = await _context.Instructors
                .Include(i => i.OfficeAssignment)
                .Include(i => i.CourseAssignments)
                    .ThenInclude(i => i.Course)
                .FirstOrDefaultAsync(s => s.ID == id);

            if (instructorToUpdate == null)
            {
                return NotFound();
            }

            if (await TryUpdateModelAsync<Instructor>(
                instructorToUpdate,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                if (String.IsNullOrWhiteSpace(
                    instructorToUpdate.OfficeAssignment?.Location))
                {
                    instructorToUpdate.OfficeAssignment = null;
                }
                UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
            PopulateAssignedCourseData(_context, instructorToUpdate);
            return Page();
        }
    }
}

앞의 코드가 하는 역할은 다음과 같습니다.

  • OfficeAssignment, CourseAssignmentCourseAssignment.Course 탐색 속성에 대한 즉시 로드를 사용하여 데이터베이스에서 현재 Instructor 엔터티를 가져옵니다.
  • 모델 바인더의 값으로 검색된 Instructor 엔터티를 업데이트합니다. TryUpdateModel초과 게시를 방지합니다.
  • 사무실 위치가 비어 있는 경우 Instructor.OfficeAssignment를 Null로 설정합니다. Instructor.OfficeAssignment가 Null인 경우 OfficeAssignment 테이블의 관련된 행이 삭제됩니다.
  • OnGetAsync에서 PopulateAssignedCourseData를 호출하여 AssignedCourseData 뷰 모델 클래스를 통해 확인란에 대한 정보를 제공합니다.
  • OnPostAsync에서 UpdateInstructorCourses를 호출하여 확인란의 정보를 편집 중인 Instructor 엔터티에 적용합니다.
  • TryUpdateModel이 실패하는 경우 OnPostAsync에서 PopulateAssignedCourseDataUpdateInstructorCourses를 호출합니다. 이 메서드 호출은 오류 메시지와 함께 다시 표시될 때 페이지에 입력된 할당된 과정 데이터를 복원합니다.

강사 편집 Razor 페이지 업데이트

다음 코드를 사용하여 Pages/Instructors/Edit.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

위의 코드는 세 개의 열이 있는 HTML 테이블을 만듭니다. 각 열에는 확인란과 과정 번호 및 제목을 포함하는 캡션이 있습니다. 모든 확인란의 이름은 동일합니다("selectedCourses"). 동일한 이름을 사용하면 모델 바인더에 그룹으로 취급하도록 알립니다. 각 확인란의 값 특성은 CourseID로 설정됩니다. 페이지가 게시되면 모델 바인더는 선택된 확인란만의 CourseID 값으로 구성된 배열을 전달합니다.

확인란이 처음 렌더링되는 경우 강사에게 할당된 과정이 선택됩니다.

참고: 강사 강좌 데이터를 편집하기 위해 여기에 적용되는 방법은 제한된 수의 강좌가 있는 경우에 잘 작동합니다. 훨씬 큰 컬렉션의 경우 다른 UI 및 다른 업데이트 메서드는 더욱 사용 가능하며 효율적입니다.

앱을 실행하고 업데이트된 강사 편집 페이지를 테스트합니다. 일부 강좌 할당을 변경합니다. 변경 내용은 인덱스 페이지에 반영됩니다.

강사 만들기 페이지 업데이트

편집 페이지와 유사한 코드를 사용하여 강사 만들기 페이지 모델 및 Razor 페이지를 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class CreateModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            var instructor = new Instructor();
            instructor.CourseAssignments = new List<CourseAssignment>();

            // Provides an empty collection for the foreach loop
            // foreach (var course in Model.AssignedCourseDataList)
            // in the Create Razor page.
            PopulateAssignedCourseData(_context, instructor);
            return Page();
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
        {
            var newInstructor = new Instructor();
            if (selectedCourses != null)
            {
                newInstructor.CourseAssignments = new List<CourseAssignment>();
                foreach (var course in selectedCourses)
                {
                    var courseToAdd = new CourseAssignment
                    {
                        CourseID = int.Parse(course)
                    };
                    newInstructor.CourseAssignments.Add(courseToAdd);
                }
            }

            if (await TryUpdateModelAsync<Instructor>(
                newInstructor,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                _context.Instructors.Add(newInstructor);                
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            PopulateAssignedCourseData(_context, newInstructor);
            return Page();
        }
    }
}
@page
@model ContosoUniversity.Pages.Instructors.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

강사 만들기 페이지를 테스트합니다.

강사 삭제 페이지 업데이트

다음 코드를 사용하여 Pages/Instructors/Delete.cshtml.cs을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor = await _context.Instructors.FirstOrDefaultAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor instructor = await _context.Instructors
                .Include(i => i.CourseAssignments)
                .SingleAsync(i => i.ID == id);

            if (instructor == null)
            {
                return RedirectToPage("./Index");
            }

            var departments = await _context.Departments
                .Where(d => d.InstructorID == id)
                .ToListAsync();
            departments.ForEach(d => d.InstructorID = null);

            _context.Instructors.Remove(instructor);

            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }
    }
}

위의 코드로 다음이 변경됩니다.

  • CourseAssignments 탐색 속성에 대해 즉시 로드를 사용합니다. CourseAssignments는 포함되어야 합니다. 또는 강사가 삭제될 때 삭제되지 않습니다. 읽을 필요가 없도록 하려면 데이터베이스에 계단식 삭제를 구성합니다.

  • 삭제될 강사가 부서의 관리자로 할당된 경우 해당 부서에서 강사 할당을 제거합니다.

앱을 실행하고 삭제 페이지를 테스트합니다.

다음 단계

이 자습서에서는 관련된 데이터 업데이트를 보여 줍니다. 해결할 수 없는 문제가 발생할 경우 완성된 앱을 다운로드하거나 봅니다.지침을 다운로드합니다.

다음 그림은 완료된 페이지의 일부를 보여 줍니다.

Course Edit pageInstructor Edit page

만들기 및 편집 강좌 페이지를 검사하고 테스트합니다. 새 강좌를 만듭니다. 부서는 해당 이름이 아닌 해당 기본 키(정수)로 선택됩니다. 새 강좌를 편집합니다. 테스트를 완료하면 새 강좌를 삭제합니다.

공통 코드를 공유하는 기본 클래스 만들기

강좌/만들기 및 강좌/페이지 편집 각각은 부서 이름의 목록이 필요합니다. 편집 및 만들기 페이지의 Pages/Courses/DepartmentNamePageModel.cshtml.cs 기본 클래스를 만듭니다.

using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
    public class DepartmentNamePageModel : PageModel
    {
        public SelectList DepartmentNameSL { get; set; }

        public void PopulateDepartmentsDropDownList(SchoolContext _context,
            object selectedDepartment = null)
        {
            var departmentsQuery = from d in _context.Departments
                                   orderby d.Name // Sort by name.
                                   select d;

            DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),
                        "DepartmentID", "Name", selectedDepartment);
        }
    }
}

앞의 코드는 부서 이름 목록을 포함하는 메서드를 만듭니다 SelectList . selectedDepartment가 지정된 경우 해당 부서는 SelectList에서 선택됩니다.

만들기 및 편집 페이지 모델 클래스는 DepartmentNamePageModel에서 파생됩니다.

강좌 페이지 사용자 지정

새 강좌 엔터티가 만들어질 때 기존 부서에 대한 관계가 있어야 합니다. 강좌를 만드는 동안 부서를 추가하기 위해 만들기 및 편집에 대한 기본 클래스는 부서를 선택하기 위한 드롭다운 목록을 포함합니다. 드롭다운 목록은 Course.DepartmentID FK(외래 키) 속성을 설정합니다. EF Core는 Course.DepartmentID FK를 사용하여 Department 탐색 속성을 로드합니다.

Create course

다음 코드로 만들기 페이지 모델을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class CreateModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            PopulateDepartmentsDropDownList(_context);
            return Page();
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var emptyCourse = new Course();

            if (await TryUpdateModelAsync<Course>(
                 emptyCourse,
                 "course",   // Prefix for form value.
                 s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
            {
                _context.Courses.Add(emptyCourse);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID);
            return Page();
        }
      }
}

앞의 코드가 하는 역할은 다음과 같습니다.

  • DepartmentNamePageModel에서 파생됩니다.
  • 초과 게시를 방지하도록 TryUpdateModelAsync를 사용합니다.
  • ViewData["DepartmentID"]DepartmentNameSL로 바꿉니다(기본 클래스에서).

ViewData["DepartmentID"]는 강력한 형식의 DepartmentNameSL로 대체됩니다. 강력한 형식의 모델은 약한 형식보다 선호됩니다. 자세한 내용은 약한 형식의 데이터(ViewData 및 ViewBag)를 참조하세요.

강좌 만들기 페이지 업데이트

다음 코드를 사용하여 Pages/Courses/Create.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
    ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <input asp-for="Course.CourseID" class="form-control" />
                <span asp-validation-for="Course.CourseID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control"
                        asp-items="@Model.DepartmentNameSL">
                    <option value="">-- Select Department --</option>
                </select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>
<div>
    <a asp-page="Index">Back to List</a>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

위의 표시로 다음이 변경됩니다.

  • 캡션을 DepartmentID에서 Department로 변경합니다.
  • "ViewBag.DepartmentID"DepartmentNameSL로 바꿉니다(기본 클래스에서).
  • "부서 선택" 옵션을 추가합니다. 이 변경 내용은 첫 번째 부서 대신 "부서 선택"을 렌더링합니다.
  • 부서가 선택되지 않은 경우 유효성 검사 메시지를 추가합니다.

Razor 페이지는 Select 태그 도우미를 사용합니다.

<div class="form-group">
    <label asp-for="Course.Department" class="control-label"></label>
    <select asp-for="Course.DepartmentID" class="form-control"
            asp-items="@Model.DepartmentNameSL">
        <option value="">-- Select Department --</option>
    </select>
    <span asp-validation-for="Course.DepartmentID" class="text-danger" />
</div>

Create 페이지를 테스트합니다. 만들기 페이지는 부서 ID보다는 부서 이름을 표시합니다.

강좌 만들기 페이지를 업데이트합니다.

Pages/Courses/Edit.cshtml.cs의 코드를 다음 코드로 바꿉니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class EditModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Course = await _context.Courses
                .Include(c => c.Department).FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }

            // Select current DepartmentID.
            PopulateDepartmentsDropDownList(_context,Course.DepartmentID);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var courseToUpdate = await _context.Courses.FindAsync(id);

            if (await TryUpdateModelAsync<Course>(
                 courseToUpdate,
                 "course",   // Prefix for form value.
                   c => c.Credits, c => c.DepartmentID, c => c.Title))
            {
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID);
            return Page();
        }       
    }
}

변경 내용은 만들기 페이지 모델에서 만든 것과 비슷합니다. 위의 코드에서 PopulateDepartmentsDropDownList는 드롭다운 목록에 지정된 부서를 선택하는 부서 ID를 전달합니다.

다음 태그를 사용하여 Pages/Courses/Edit.cshtml을 업데이트합니다.

@page
@model ContosoUniversity.Pages.Courses.EditModel

@{
    ViewData["Title"] = "Edit";
}

<h2>Edit</h2>

<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Course.CourseID" />
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <div>@Html.DisplayFor(model => model.Course.CourseID)</div>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control" 
                        asp-items="@Model.DepartmentNameSL"></select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

위의 표시로 다음이 변경됩니다.

  • 강좌 ID를 표시합니다. 일반적으로 엔터티의 PK(기본 키)는 표시되지 않습니다. PK는 일반적으로 사용자에게 아무런 의미가 없습니다. 이 경우 PK는 강좌 번호입니다.
  • 캡션을 DepartmentID에서 Department로 변경합니다.
  • "ViewBag.DepartmentID"DepartmentNameSL로 바꿉니다(기본 클래스에서).

페이지는 강좌 번호에 대한 숨겨진 필드(<input type="hidden">)를 포함합니다. asp-for="Course.CourseID"<label> 태그 도우미를 추가하는 것은 숨겨진 필드에 대한 필요성을 제거하지 않습니다. <input type="hidden">은 사용자가 저장을 클릭할 때 게시된 데이터에 포함되도록 강좌 번호에 필요합니다.

업데이트된 코드를 테스트합니다. 강좌를 만들고, 편집하고, 삭제합니다.

세부 정보 및 삭제 페이지 모델에 AsNoTracking 추가

AsNoTracking 는 추적이 필요하지 않을 때 성능을 향상시킬 수 있습니다. 삭제 및 세부 정보 페이지 모델에 AsNoTracking을 추가합니다. 다음 코드에서는 업데이트된 삭제 페이지 모델을 보여 줍니다.

public class DeleteModel : PageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public DeleteModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Course Course { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Course = await _context.Courses
            .AsNoTracking()
            .Include(c => c.Department)
            .FirstOrDefaultAsync(m => m.CourseID == id);

        if (Course == null)
        {
            return NotFound();
        }
        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Course = await _context.Courses
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.CourseID == id);

        if (Course != null)
        {
            _context.Courses.Remove(Course);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage("./Index");
    }
}

파일에서 OnGetAsync 메서드를 업데이트합니다.Pages/Courses/Details.cshtml.cs

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Course = await _context.Courses
         .AsNoTracking()
         .Include(c => c.Department)
         .FirstOrDefaultAsync(m => m.CourseID == id);

    if (Course == null)
    {
        return NotFound();
    }
    return Page();
}

삭제 및 세부 정보 페이지 수정

다음 표시로 삭제 Razor 페이지를 업데이트합니다.

@page
@model ContosoUniversity.Pages.Courses.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Course</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Course.CourseID)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.Department.DepartmentID)
        </dd>
    </dl>
    
    <form method="post">
        <input type="hidden" asp-for="Course.CourseID" />
        <input type="submit" value="Delete" class="btn btn-default" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

세부 정보 페이지에 동일한 변경 내용을 만듭니다.

강좌 페이지 테스트

만들기, 편집, 세부 정보 및 삭제를 테스트합니다.

강사 페이지 업데이트

다음 섹션에서는 강사 페이지를 업데이트합니다.

사무실 위치 추가

강사 레코드를 편집할 때 강사의 사무실 할당을 업데이트할 수 있습니다. Instructor 엔터티에는 OfficeAssignment 엔터티와 일대영 또는 일 관계가 있습니다. 강사 코드를 처리해야 합니다.

  • 사용자가 사무실 할당을 해제하는 경우 OfficeAssignment 엔터티를 삭제합니다.
  • 사용자가 사무실 할당을 입력하고 비어 있던 경우 새 OfficeAssignment 엔터티를 만듭니다.
  • 사용자가 사무실 할당을 변경하는 경우 OfficeAssignment 엔터티를 업데이트합니다.

다음 코드로 강사 편집 페이지 모델을 업데이트합니다.

public class EditModel : PageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public EditModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Instructor Instructor { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Instructor = await _context.Instructors
            .Include(i => i.OfficeAssignment)
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.ID == id);

        if (Instructor == null)
        {
            return NotFound();
        }
        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        var instructorToUpdate = await _context.Instructors
            .Include(i => i.OfficeAssignment)
            .FirstOrDefaultAsync(s => s.ID == id);

        if (await TryUpdateModelAsync<Instructor>(
            instructorToUpdate,
            "Instructor",
            i => i.FirstMidName, i => i.LastName, 
            i => i.HireDate, i => i.OfficeAssignment))
        {
            if (String.IsNullOrWhiteSpace(
                instructorToUpdate.OfficeAssignment?.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }
            await _context.SaveChangesAsync();
        }
        return RedirectToPage("./Index");

    }
}

앞의 코드가 하는 역할은 다음과 같습니다.

  • OfficeAssignment 탐색 속성에 대한 즉시 로드를 사용하여 데이터베이스에서 현재 Instructor 엔터티를 가져옵니다.
  • 모델 바인더의 값으로 검색된 Instructor 엔터티를 업데이트합니다. TryUpdateModel초과 게시를 방지합니다.
  • 사무실 위치가 비어 있는 경우 Instructor.OfficeAssignment를 Null로 설정합니다. Instructor.OfficeAssignment가 Null인 경우 OfficeAssignment 테이블의 관련된 행이 삭제됩니다.

강사 편집 페이지 업데이트

사무실 위치로 업데이트 Pages/Instructors/Edit.cshtml 합니다.

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

강사 사무실 위치를 변경할 수 있음을 확인합니다.

강사 편집 페이지에 강좌 할당 추가

강사는 강좌 수에 관계 없이 가르칠 수 있습니다. 이 섹션에서는 강좌 할당을 변경하는 기능을 추가합니다. 다음 그림에서는 업데이트된 강사 편집 페이지를 보여 줍니다.

Instructor Edit page with courses

CourseInstructor에는 다대다 관계가 있습니다. 관계를 추가하고 제거하려면 CourseAssignments 조인 엔터티 집합에서 엔터티를 추가하고 제거합니다.

확인란을 선택하면 강사에게 할당된 과정에 변경 내용이 적용됩니다. 데이터베이스의 모든 과정에 해당하는 확인란이 표시됩니다. 강사에게 할당된 강좌가 확인됩니다. 사용자는 확인란을 선택하거나 선택 취소하여 과정 할당을 변경할 수 있습니다. 강좌의 번호가 훨씬 더 큰 경우:

  • 아마도 강좌를 표시하는 데 다른 사용자 인터페이스를 사용합니다.
  • 관계를 만들거나 삭제하는 조인 엔터티를 조작하는 방법은 변경되지 않습니다.

만들기 및 편집 강사 페이지를 지원하는 클래스 추가

다음 코드를 사용하여 만듭니 Models/SchoolViewModels/AssignedCourseData.cs 다.

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

AssignedCourseData 클래스는 강사별 할당된 강좌에 대한 확인란을 만드는 데이터를 포함합니다.

Pages/Instructors/InstructorCoursesPageModel.cshtml.cs 기본 클래스를 만듭니다.

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
    public class InstructorCoursesPageModel : PageModel
    {

        public List<AssignedCourseData> AssignedCourseDataList;

        public void PopulateAssignedCourseData(SchoolContext context, 
                                               Instructor instructor)
        {
            var allCourses = context.Courses;
            var instructorCourses = new HashSet<int>(
                instructor.CourseAssignments.Select(c => c.CourseID));
            AssignedCourseDataList = new List<AssignedCourseData>();
            foreach (var course in allCourses)
            {
                AssignedCourseDataList.Add(new AssignedCourseData
                {
                    CourseID = course.CourseID,
                    Title = course.Title,
                    Assigned = instructorCourses.Contains(course.CourseID)
                });
            }
        }

        public void UpdateInstructorCourses(SchoolContext context, 
            string[] selectedCourses, Instructor instructorToUpdate)
        {
            if (selectedCourses == null)
            {
                instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
                return;
            }

            var selectedCoursesHS = new HashSet<string>(selectedCourses);
            var instructorCourses = new HashSet<int>
                (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
            foreach (var course in context.Courses)
            {
                if (selectedCoursesHS.Contains(course.CourseID.ToString()))
                {
                    if (!instructorCourses.Contains(course.CourseID))
                    {
                        instructorToUpdate.CourseAssignments.Add(
                            new CourseAssignment
                            {
                                InstructorID = instructorToUpdate.ID,
                                CourseID = course.CourseID
                            });
                    }
                }
                else
                {
                    if (instructorCourses.Contains(course.CourseID))
                    {
                        CourseAssignment courseToRemove
                            = instructorToUpdate
                                .CourseAssignments
                                .SingleOrDefault(i => i.CourseID == course.CourseID);
                        context.Remove(courseToRemove);
                    }
                }
            }
        }
    }
}

InstructorCoursesPageModel은 편집 및 만들기 페이지 모델에 사용하는 기본 클래스입니다. PopulateAssignedCourseDataAssignedCourseDataList를 채우도록 모든 Course 엔터티를 읽습니다. 각 강좌의 경우 코드는 CourseID, 제목 및 강사가 강좌에 할당되었는지 여부를 설정합니다. HashSet는 효율적인 조회를 만드는 데 사용됩니다.

강사 편집 페이지 모델

다음 코드로 강사 편집 페이지 모델을 업데이트합니다.

public class EditModel : InstructorCoursesPageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public EditModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Instructor Instructor { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Instructor = await _context.Instructors
            .Include(i => i.OfficeAssignment)
            .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.ID == id);

        if (Instructor == null)
        {
            return NotFound();
        }
        PopulateAssignedCourseData(_context, Instructor);
        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id, string[] selectedCourses)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        var instructorToUpdate = await _context.Instructors
            .Include(i => i.OfficeAssignment)
            .Include(i => i.CourseAssignments)
                .ThenInclude(i => i.Course)
            .FirstOrDefaultAsync(s => s.ID == id);

        if (await TryUpdateModelAsync<Instructor>(
            instructorToUpdate,
            "Instructor",
            i => i.FirstMidName, i => i.LastName,
            i => i.HireDate, i => i.OfficeAssignment))
        {
            if (String.IsNullOrWhiteSpace(
                instructorToUpdate.OfficeAssignment?.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }
            UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }
        UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
        PopulateAssignedCourseData(_context, instructorToUpdate);
        return Page();
    }
}

위의 코드는 사무실 할당 변경을 처리합니다.

강사 Razor 뷰를 업데이트합니다.

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

참고 항목

Visual Studio에서 코드를 붙여 넣을 때 줄 바꿈이 코드를 중단하는 방식으로 변경됩니다. 자동 서식 지정을 실행 취소하려면 Ctrl+Z를 한 번 누릅니다. Ctrl+Z는 여기와 같은 모양이 되도록 줄 바꿈을 수정합니다. 들여쓰기는 완벽할 필요가 없지만 @:</tr><tr>, @:<td>, @:</td>@:</tr> 줄은 표시된 것처럼 각각 한 줄에 있어야 합니다. 선택된 새 코드의 블록과 함께 Tab 키를 세 번 눌러 기존 코드와 함께 새 코드를 정렬합니다. 이 링크를 통해 이 버그의 상태를 투표하거나 검토합니다.

위의 코드는 세 개의 열이 있는 HTML 테이블을 만듭니다. 각 열에는 확인란과 과정 번호 및 제목을 포함하는 캡션이 있습니다. 모든 확인란의 이름은 동일합니다("selectedCourses"). 동일한 이름을 사용하면 모델 바인더에 그룹으로 취급하도록 알립니다. 각 확인란의 값 특성은 CourseID로 설정됩니다. 페이지가 게시되면 모델 바인더는 선택된 확인란만의 CourseID 값으로 구성된 배열을 전달합니다.

확인란이 처음 렌더링되는 경우 강사에게 할당된 강좌는 특성을 확인했습니다.

앱을 실행하고 업데이트된 강사 편집 페이지를 테스트합니다. 일부 강좌 할당을 변경합니다. 변경 내용은 인덱스 페이지에 반영됩니다.

참고: 강사 강좌 데이터를 편집하기 위해 여기에 적용되는 방법은 제한된 수의 강좌가 있는 경우에 잘 작동합니다. 훨씬 큰 컬렉션의 경우 다른 UI 및 다른 업데이트 메서드는 더욱 사용 가능하며 효율적입니다.

강사 만들기 페이지 업데이트

다음 코드로 강사 만들기 페이지 모델을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class CreateModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            var instructor = new Instructor();
            instructor.CourseAssignments = new List<CourseAssignment>();

            // Provides an empty collection for the foreach loop
            // foreach (var course in Model.AssignedCourseDataList)
            // in the Create Razor page.
            PopulateAssignedCourseData(_context, instructor);
            return Page();
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var newInstructor = new Instructor();
            if (selectedCourses != null)
            {
                newInstructor.CourseAssignments = new List<CourseAssignment>();
                foreach (var course in selectedCourses)
                {
                    var courseToAdd = new CourseAssignment
                    {
                        CourseID = int.Parse(course)
                    };
                    newInstructor.CourseAssignments.Add(courseToAdd);
                }
            }

            if (await TryUpdateModelAsync<Instructor>(
                newInstructor,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                _context.Instructors.Add(newInstructor);                
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            PopulateAssignedCourseData(_context, newInstructor);
            return Page();
        }
    }
}

앞의 코드는 코드와 Pages/Instructors/Edit.cshtml.cs 비슷합니다.

다음 변경 내용으로 강사 만들기 Razor 페이지를 업데이트합니다.

@page
@model ContosoUniversity.Pages.Instructors.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

강사 만들기 페이지를 테스트합니다.

Delete 페이지 업데이트

다음 코드로 삭제 페이지 모델을 업데이트합니다.

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor = await _context.Instructors.SingleAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int id)
        {
            Instructor instructor = await _context.Instructors
                .Include(i => i.CourseAssignments)
                .SingleAsync(i => i.ID == id);

            var departments = await _context.Departments
                .Where(d => d.InstructorID == id)
                .ToListAsync();
            departments.ForEach(d => d.InstructorID = null);

            _context.Instructors.Remove(instructor);

            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }
    }
}

위의 코드로 다음이 변경됩니다.

  • CourseAssignments 탐색 속성에 대해 즉시 로드를 사용합니다. CourseAssignments는 포함되어야 합니다. 또는 강사가 삭제될 때 삭제되지 않습니다. 읽을 필요가 없도록 하려면 데이터베이스에 계단식 삭제를 구성합니다.

  • 삭제될 강사가 부서의 관리자로 할당된 경우 해당 부서에서 강사 할당을 제거합니다.

추가 리소스