자습서: ASP.NET MVC 앱에서 EF를 사용하여 관련 데이터 읽기

이전 자습서에서는 School 데이터 모델을 완료했습니다. 이 자습서에서는 관련 데이터, 즉 Entity Framework가 탐색 속성에 로드하는 데이터를 읽고 표시합니다.

다음 그림에서는 사용할 페이지를 보여 줍니다.

과정 목록이 있는 과정 페이지를 보여 주는 스크린샷

Instructors_index_page_with_instructor_and_course_selected

완료된 프로젝트 다운로드

Contoso University 샘플 웹 애플리케이션은 Entity Framework 6 Code First 및 Visual Studio를 사용하여 ASP.NET MVC 5 애플리케이션을 만드는 방법을 보여 줍니다. 자습서 시리즈에 대한 정보는 시리즈의 첫 번째 자습서를 참조하세요.

이 자습서에서는 다음과 같은 작업을 수행합니다.

  • 관련 데이터를 로드하는 방법 알아보기
  • 강좌 페이지 만들기
  • 강사 페이지 만들기

사전 요구 사항

Entity Framework는 엔터티의 탐색 속성에 관련 데이터를 로드할 수 있는 여러 가지 방법이 있습니다.

  • 지연 로드. 엔터티를 처음 읽을 때 관련된 데이터가 검색되지 않습니다. 그러나 탐색 속성에 처음으로 액세스하려고 할 때 해당 탐색 속성에 필요한 데이터가 자동으로 검색됩니다. 이렇게 하면 엔터티 자체에 대한 쿼리와 엔터티에 대한 관련 데이터를 검색해야 할 때마다 하나씩 여러 쿼리가 데이터베이스로 전송됩니다. 클래스는 DbContext 기본적으로 지연 로드를 사용하도록 설정합니다.

    Lazy_loading_example

  • 즉시 로드. 엔터티를 읽을 때 관련된 데이터가 함께 검색됩니다. 이는 일반적으로 필요한 데이터를 모두 검색하는 단일 조인 쿼리를 발생시킵니다. 메서드를 사용하여 즉시 로드를 지정합니다 Include .

    Eager_loading_example

  • 명시적 로드. 코드에서 관련 데이터를 명시적으로 검색한다는 점을 제외하고 지연 로드와 비슷합니다. 탐색 속성에 액세스할 때 자동으로 발생하지 않습니다. 엔터티에 대한 개체 상태 관리자 항목을 가져오고 컬렉션에 대한 Collection.Load 메서드 또는 단일 엔터티를 보유하는 속성에 대해 Reference.Load 메서드를 호출하여 관련 데이터를 수동으로 로드합니다. (다음 예제에서는 관리자 탐색 속성을 로드하려는 경우 을 Reference(x => x.Administrator)로 바꿉 Collection(x => x.Courses) 니다.) 일반적으로 지연 로드를 해제한 경우에만 명시적 로드를 사용합니다.

    Explicit_loading_example

속성 값을 즉시 검색하지 않으므로 지연 로드 및 명시적 로드를 모두 지연 로드라고도 합니다.

성능 고려 사항

검색된 모든 엔터티에 대해 관련된 데이터가 필요한 경우 데이터베이스에 전송된 단일 쿼리는 검색된 각 엔터티에 대한 별도 쿼리보다 일반적으로 더 효율적이므로 즉시 로드는 종종 최상의 성능을 제공합니다. 예를 들어 위의 예제에서 각 부서에 10개의 관련 과정이 있다고 가정합니다. 즉시 로드 예제를 사용하면 단일(조인) 쿼리와 데이터베이스로의 단일 왕복만 발생합니다. 지연 로드 및 명시적 로드 예제는 모두 11개의 쿼리와 11번의 데이터베이스 왕복을 초래합니다. 데이터베이스에 대한 추가 왕복은 대기 시간이 길 때 성능에 특히 악영향을 줍니다.

반면에 일부 시나리오에서는 지연 로드가 더 효율적입니다. 즉시 로드하면 매우 복잡한 조인이 생성되어 SQL Server 효율적으로 처리할 수 없습니다. 또는 처리 중인 엔터티 집합의 하위 집합에 대해서만 엔터티의 탐색 속성에 액세스해야 하는 경우 지연 로드는 필요한 것보다 더 많은 데이터를 검색하기 때문에 지연 로드가 더 잘 수행될 수 있습니다. 성능이 중요한 경우 최상의 선택을 위해 두 가지 방식으로 성능을 테스트하는 것이 가장 좋습니다.

지연 로드는 성능 문제를 일으키는 코드를 마스킹할 수 있습니다. 예를 들어 열성 또는 명시적 로드를 지정하지 않지만 대량의 엔터티를 처리하고 각 반복에서 여러 탐색 속성을 사용하는 코드는 매우 비효율적일 수 있습니다(데이터베이스로의 많은 왕복으로 인해). 온-프레미스 SQL 서버를 사용하여 개발에서 잘 작동하는 애플리케이션은 대기 시간 증가 및 지연 로드로 인해 Azure SQL Database로 이동할 때 성능 문제가 있을 수 있습니다. 실제 테스트 로드를 사용하여 데이터베이스 쿼리를 프로파일링하면 지연 로드가 적절한지 확인하는 데 도움이 됩니다. 자세한 내용은 Entity Framework 전략 무시: 관련 데이터 로드Entity Framework를 사용하여 네트워크 대기 시간을 줄여 SQL Azure.

serialization 전에 지연 로드 사용 안 함

serialization 중에 지연 로드를 사용하도록 설정한 상태로 두면 의도한 것보다 훨씬 많은 데이터를 쿼리할 수 있습니다. 직렬화는 일반적으로 형식의 instance 각 속성에 액세스하여 작동합니다. 속성 액세스는 지연 로드를 트리거하고 지연 로드된 엔터티는 직렬화됩니다. 그런 다음 serialization 프로세스는 지연 로드된 엔터티의 각 속성에 액세스하여 더 지연 로드 및 serialization을 유발할 수 있습니다. 이 런어웨이 체인 반응을 방지하려면 엔터티를 직렬화하기 전에 지연 로드를 끕니다.

고급 시나리오 자습서에 설명된 대로 Entity Framework에서 사용하는 프록시 클래스로 인해 직렬화가 복잡해질 수도 있습니다.

Serialization 문제를 방지하는 한 가지 방법은 Entity Framework에서 Web API 사용 자습서와 같이 엔터티 개체 대신 DDO(데이터 전송 개체)를 직렬화하는 것입니다.

DDO를 사용하지 않는 경우 지연 로드를 사용하지 않도록 설정하고 프록시 만들기를 사용하지 않도록 설정하여 프록시 문제를 방지할 수 있습니다.

지연 로드를 사용하지 않도록 설정하는 몇 가지 다른 방법은 다음과 같습니다.

  • 특정 탐색 속성의 경우 속성을 선언할 virtual 때 키워드(keyword) 생략합니다.

  • 모든 탐색 속성의 경우 를 로 설정 LazyLoadingEnabled 하면 false컨텍스트 클래스의 생성자에 다음 코드를 입력합니다.

    this.Configuration.LazyLoadingEnabled = false;
    

강좌 페이지 만들기

Course 엔터티는 강좌에 할당된 부서의 Department 엔터티를 포함하는 탐색 속성을 포함합니다. 과정 목록에 할당된 부서의 이름을 표시하려면 탐색 속성에 있는 Course.Department 엔터티에서 Department 속성을 가져와 Name 야 합니다.

이전에 컨트롤러에 대해 Course 했던 Entity Framework 스캐폴더를 사용하여 뷰가 있는 MVC 5 컨트롤러에 대해 동일한 옵션을 사용하여 엔터티 형식에 대해 Student 이름이 CourseController CoursesController가 아닌 컨트롤러를 만듭니다.

설정
모델 클래스 과정(ContosoUniversity.Models)을 선택합니다.
데이터 컨텍스트 클래스 SchoolContext(ContosoUniversity.DAL)를 선택합니다.
컨트롤러 이름 CourseController를 입력합니다. 다시 말하지만, 을 사용하는CoursesController가 아닙니다. 과정(ContosoUniversity.Models)을 선택하면 컨트롤러 이름 값이 자동으로 채워집니다. 값을 변경해야 합니다.

다른 기본값을 그대로 두고 컨트롤러를 추가합니다.

Controllers\CourseController.cs를 열고 메서드를 Index 확인합니다.

public ActionResult Index()
{
    var courses = db.Courses.Include(c => c.Department);
    return View(courses.ToList());
}

자동 스캐폴딩은 Include 메서드를 사용하여 Department 탐색 속성에 대해 즉시 로드를 지정했습니다.

Views\Course\Index.cshtml을 열고 템플릿 코드를 다음 코드로 바꿉니다. 변경 내용이 강조 표시되어 있습니다.

@model IEnumerable<ContosoUniversity.Models.Course>

@{
    ViewBag.Title = "Courses";
}

<h2>Courses</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.CourseID)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Title)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Credits)
        </th>
        <th>
            Department
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.CourseID)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Title)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Credits)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Department.Name)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.CourseID }) |
            @Html.ActionLink("Details", "Details", new { id=item.CourseID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.CourseID })
        </td>
    </tr>
}

</table>

스캐폴드 코드에 다음 변경 내용을 만들었습니다.

  • 제목을 Index에서 Courses로 변경했습니다.
  • CourseID 속성 값을 보여 주는 Number 열을 추가했습니다. 기본적으로 기본 키는 일반적으로 최종 사용자에게 의미가 없으므로 스캐폴드되지 않습니다. 그러나 이 경우 기본 키는 의미가 있으며 표시하길 원합니다.
  • 부서 열을 오른쪽으로 이동하고 제목을 변경했습니다. 스캐폴더가 엔터티에서 NameDepartment 속성을 표시하도록 올바르게 선택했지만, 여기 과정 페이지의 열 제목은 이름이 아닌 Department여야 합니다.

Department 열의 경우 스캐폴드된 코드는 탐색 속성에 Department 로드된 엔터티의 Department 속성을 표시 Name 합니다.

<td>
    @Html.DisplayFor(modelItem => item.Department.Name)
</td>

페이지를 실행(Contoso University 홈페이지에서 과정 탭 선택)하여 부서 이름이 있는 목록을 확인합니다.

강사 페이지 만들기

이 섹션에서는 강사 페이지를 표시하기 위해 엔터티에 대한 Instructor 컨트롤러 및 보기를 만듭니다. 이 페이지는 다음과 같은 방법으로 관련된 데이터를 읽고 표시합니다.

  • 강사 목록은 OfficeAssignment 엔터티에서 관련된 데이터를 표시합니다. InstructorOfficeAssignment 엔터티는 일대영 또는 일 관계에 있습니다. OfficeAssignment 엔터티에 대해 즉시 로드를 사용합니다. 이전에 설명한 대로 기본 테이블의 검색된 모든 행에 관련된 데이터가 필요한 경우 즉시 로드는 일반적으로 더 효율적입니다. 이 경우 표시된 모든 강사에 대한 사무실 할당을 표시하길 원합니다.
  • 사용자가 강사를 선택하면 관련된 Course 엔터티가 표시됩니다. InstructorCourse 엔터티는 다대다 관계에 있습니다. Course 엔터티 및 관련된 Department 엔터티에 대해 즉시 로드를 사용합니다. 이 경우 선택한 강사만 강좌가 필요하므로 지연 로드가 더 효율적일 수 있습니다. 그러나 이 예제에서는 탐색 속성에 있는 엔터티 내에서 탐색 속성에 대한 즉시 로드를 사용하는 방법을 보여 줍니다.
  • 사용자가 과정을 선택하면 Enrollments 엔터티 집합의 관련 데이터가 표시됩니다. CourseEnrollment 엔터티는 일대다 관계에 있습니다. 엔터티 및 해당 관련 Student 엔터티에 대한 Enrollment 명시적 로드를 추가합니다. (지연 로드를 사용하도록 설정했기 때문에 명시적 로드는 필요하지 않지만 명시적 로드를 수행하는 방법을 보여 줍니다.)

강사 인덱스 보기에 대한 보기 모델 만들기

강사 페이지에는 세 개의 다른 테이블이 표시됩니다. 따라서 각각이 테이블 중 하나에 대한 데이터를 보유하는 세 가지 속성을 포함하는 보기 모델을 만듭니다.

ViewModels 폴더에서 InstructorIndexData.cs를 만들고 기존 코드를 다음 코드로 바꿉니다.

using System.Collections.Generic;
using ContosoUniversity.Models;

namespace ContosoUniversity.ViewModels
{
    public class InstructorIndexData
    {
        public IEnumerable<Instructor> Instructors { get; set; }
        public IEnumerable<Course> Courses { get; set; }
        public IEnumerable<Enrollment> Enrollments { get; set; }
    }
}

강사 컨트롤러 및 뷰 만들기

EF 읽기/쓰기 작업을 사용하여 InstructorController (InstructorsController 아님) 컨트롤러를 만듭니다.

설정
모델 클래스 강사(ContosoUniversity.Models)를 선택합니다.
데이터 컨텍스트 클래스 SchoolContext(ContosoUniversity.DAL)를 선택합니다.
컨트롤러 이름 InstructorController를 입력합니다. 다시 말하지만, 가 있는InstructorsController가 아닙니다. 과정(ContosoUniversity.Models)을 선택하면 컨트롤러 이름 값이 자동으로 채워집니다. 값을 변경해야 합니다.

다른 기본값을 그대로 두고 컨트롤러를 추가합니다.

Controllers\InstructorController.cs를using 열고 네임스페이 ViewModels 스에 대한 문을 추가합니다.

using ContosoUniversity.ViewModels;

메서드의 Index 스캐폴드된 코드는 탐색 속성에 대해서만 OfficeAssignment 즉시 로드를 지정합니다.

public ActionResult Index()
{
    var instructors = db.Instructors.Include(i => i.OfficeAssignment);
    return View(instructors.ToList());
}

메서드를 Index 다음 코드로 바꿔 추가 관련 데이터를 로드하고 보기 모델에 넣습니다.

public ActionResult Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();
    viewModel.Instructors = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses.Select(c => c.Department))
        .OrderBy(i => i.LastName);

    if (id != null)
    {
        ViewBag.InstructorID = id.Value;
        viewModel.Courses = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single().Courses;
    }

    if (courseID != null)
    {
        ViewBag.CourseID = courseID.Value;
        viewModel.Enrollments = viewModel.Courses.Where(
            x => x.CourseID == courseID).Single().Enrollments;
    }

    return View(viewModel);
}

메서드는 선택한 강사 및 선택한 과정의 ID 값을 제공하고 필요한 모든 데이터를 뷰에 전달하는 선택적 경로 데이터(id) 및 쿼리 문자열 매개 변수(courseID)를 허용합니다. 매개 변수는 페이지의 선택 하이퍼링크에서 제공됩니다.

코드는 보기 모델의 인스턴스를 만들고 강사 목록에 배치하여 시작합니다. 코드는 및 탐색 속성에 대한 Instructor.OfficeAssignment 즉시 로드를 Instructor.Courses 지정합니다.

var viewModel = new InstructorIndexData();
viewModel.Instructors = db.Instructors
    .Include(i => i.OfficeAssignment)
    .Include(i => i.Courses.Select(c => c.Department))
     .OrderBy(i => i.LastName);

두 번째 Include 메서드는 Courses를 로드하고 로드되는 각 과정에 대해 탐색 속성에 대한 즉시 로드를 Course.Department 수행합니다.

.Include(i => i.Courses.Select(c => c.Department))

앞에서 설명한 것처럼 즉시 로드할 필요는 없지만 성능을 향상시키기 위해 수행됩니다. 보기는 항상 OfficeAssignment 엔터티가 필요하므로 동일한 쿼리에서 페치하는 것이 더 효율적입니다. Course 웹 페이지에서 강사를 선택할 때 엔터티가 필요하므로 빈도 로드는 페이지가 없는 것보다 선택한 과정으로 더 자주 표시되는 경우에만 지연 로드보다 낫습니다.

강사 ID를 선택한 경우 선택한 강사는 보기 모델의 강사 목록에서 검색됩니다. 뷰 모델의 Courses 속성은 해당 강사의 Courses 탐색 속성에서 Course 엔터티로 로드됩니다.

if (id != null)
{
    ViewBag.InstructorID = id.Value;
    viewModel.Courses = viewModel.Instructors.Where(i => i.ID == id.Value).Single().Courses;
}

메서드는 Where 컬렉션을 반환하지만, 이 경우 해당 메서드에 전달된 조건에 따라 단일 Instructor 엔터티만 반환됩니다. Single 메서드는 컬렉션을 단일 Instructor 엔터티로 변환합니다. 이는 해당 엔터티의 Courses 속성에 대한 액세스를 제공합니다.

컬렉션에 항목이 하나만 있다는 것을 알고 있는 경우 컬렉션에서 Single 메서드를 사용합니다. 메서드는 Single 전달된 컬렉션이 비어 있거나 둘 이상의 항목이 있는 경우 예외를 throw합니다. 또는 컬렉션이 비어 있는 경우 기본값(null이 경우)을 반환하는 SingleOrDefault가 있습니다. 그러나 이 경우 여전히 예외가 발생하며(참조에서 속성을 null 찾으 Courses 려고 시도함) 예외 메시지는 문제의 원인을 덜 명확하게 나타냅니다. 메서드를 호출할 때 메서드를 Single 별도로 호출 Where 하는 대신 조건을 전달할 Where 수도 있습니다.

.Single(i => i.ID == id.Value)

위 코드를 아래 코드 대신 사용합니다.

.Where(I => i.ID == id.Value).Single()

다음으로 강좌를 선택한 경우 선택한 강좌가 보기 모델의 강좌 목록에서 검색됩니다. 그런 다음 보기 모델의 Enrollments 속성이 해당 과정의 Enrollments 탐색 속성에 Enrollment 있는 엔터티와 함께 로드됩니다.

if (courseID != null)
{
    ViewBag.CourseID = courseID.Value;
    viewModel.Enrollments = viewModel.Courses.Where(
        x => x.CourseID == courseID).Single().Enrollments;
}

강사 인덱스 보기 수정

Views\Instructor\Index.cshtml에서 템플릿 코드를 다음 코드로 바꿉니다. 변경 내용은 강조 표시되어 있습니다.

@model ContosoUniversity.ViewModels.InstructorIndexData

@{
    ViewBag.Title = "Instructors";
}

<h2>Instructors</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Hire Date</th>
        <th>Office</th>
        <th></th>
    </tr>

    @foreach (var item in Model.Instructors)
    {
        string selectedRow = "";
        if (item.ID == ViewBag.InstructorID)
        {
            selectedRow = "success";
        }
        <tr class="@selectedRow">
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.HireDate)
            </td>
            <td>
                @if (item.OfficeAssignment != null)
                {
                    @item.OfficeAssignment.Location
                }
            </td>
            <td>
                @Html.ActionLink("Select", "Index", new { id = item.ID }) |
                @Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
                @Html.ActionLink("Details", "Details", new { id = item.ID }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.ID })
            </td>
        </tr>
    }

    </table>

기존 코드에 다음 변경 내용을 만들었습니다.

  • 모델 클래스를 InstructorIndexData로 변경했습니다.

  • 페이지 제목을 인덱스에서 강사로 변경했습니다.

  • 가 null이 아닌 경우에만 item.OfficeAssignment 표시되는 item.OfficeAssignment.LocationOffice 열이 추가되었습니다. (1 대 0 또는 1 관계이므로 관련 OfficeAssignment 엔터티가 없을 수 있습니다.)

    <td> 
        @if (item.OfficeAssignment != null) 
        { 
            @item.OfficeAssignment.Location  
        } 
    </td>
    
  • 선택한 강사의 요소에 tr 동적으로 추가할 class="success" 코드가 추가되었습니다. 그러면 부트스트랩 클래스를 사용하여 선택한 행의 배경색이 설정됩니다.

    string selectedRow = ""; 
    if (item.InstructorID == ViewBag.InstructorID) 
    { 
        selectedRow = "success"; 
    } 
    <tr class="@selectedRow" valign="top">
    
  • 각 행의 다른 링크 바로 앞에 Select라는 레이블이 새로 ActionLink 추가되어 선택한 강사 ID가 메서드로 Index 전송됩니다.

애플리케이션을 실행하고 강사 탭을 선택합니다. 페이지에는 관련 엔터티가 Location 없는 OfficeAssignment 경우 관련 OfficeAssignment 엔터티 및 빈 테이블 셀의 속성이 표시됩니다.

Views\Instructor\Index.cshtml 파일의 닫는 table 요소(파일 끝에 있음) 다음에 다음 코드를 추가합니다. 이 코드는 강사가 선택된 경우 강사와 관련된 강좌의 목록을 표시합니다.

@if (Model.Courses != null)
{
    <h3>Courses Taught by Selected Instructor</h3>
    <table class="table">
        <tr>
            <th></th>
            <th>Number</th>
            <th>Title</th>
            <th>Department</th>
        </tr>

        @foreach (var item in Model.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == ViewBag.CourseID)
            {
                selectedRow = "success";
            }
            <tr class="@selectedRow">
                <td>
                    @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr>
        }

    </table>
}

이 코드는 보기 모델의 Courses 속성을 읽어 강좌의 목록을 표시합니다. 또한 선택한 과정의 ID를 작업 메서드로 보내는 하이퍼링크를 Index 제공합니다Select.

페이지를 실행하고 강사를 선택합니다. 이제 선택된 강사에 할당된 강좌를 표시하는 표가 표시되고 각 강좌에 대해 할당된 부서의 이름이 표시됩니다.

방금 추가한 코드 블록 뒤에 다음 코드를 추가합니다. 해당 강좌가 선택된 경우에 강좌에 등록된 학생의 목록을 표시합니다.

@if (Model.Enrollments != null)
{
    <h3>
        Students Enrolled in Selected Course
    </h3>
    <table class="table">
        <tr>
            <th>Name</th>
            <th>Grade</th>
        </tr>
        @foreach (var item in Model.Enrollments)
        {
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
}

이 코드는 강좌에 등록한 학생의 목록을 표시하기 위해 보기 모델의 Enrollments 속성을 읽습니다.

페이지를 실행하고 강사를 선택합니다. 그런 다음, 강좌를 선택하여 등록된 학생 및 해당 등급의 목록을 봅니다.

명시적 로드 추가

InstructorController.cs를 열고 메서드가 Index 선택한 과정의 등록 목록을 가져오는 방법을 살펴봅니다.

if (courseID != null)
{
    ViewBag.CourseID = courseID.Value;
    viewModel.Enrollments = viewModel.Courses.Where(
        x => x.CourseID == courseID).Single().Enrollments;
}

강사 목록을 검색할 때 탐색 속성 및 각 과정의 속성에 대한 Courses 즉시 로드를 Department 지정했습니다. 그런 다음 뷰 모델에 컬렉션을 배치 Courses 하고 이제 해당 컬렉션의 한 엔터티에서 탐색 속성에 액세스합니다 Enrollments . 탐색 속성에 대한 Course.Enrollments 즉시 로드를 지정하지 않았기 때문에 지연 로드의 결과로 해당 속성의 데이터가 페이지에 표시됩니다.

다른 방법으로 코드를 변경하지 않고 지연 로드를 사용하지 않도록 설정한 경우 속성은 Enrollments 코스의 실제로 등록 수에 관계없이 null이 됩니다. 이 경우 속성을 로드 Enrollments 하려면 즉시 로드 또는 명시적 로드를 지정해야 합니다. 이미 즉시 로드하는 방법을 살펴보았습니다. 명시적 로드 예제를 보려면 메서드를 Index 속성을 명시적으로 로드하는 다음 코드로 바꿉니다 Enrollments . 변경된 코드가 강조 표시됩니다.

public ActionResult Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();

    viewModel.Instructors = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses.Select(c => c.Department))
        .OrderBy(i => i.LastName);

    if (id != null)
    {
        ViewBag.InstructorID = id.Value;
        viewModel.Courses = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single().Courses;
    }
    
    if (courseID != null)
    {
        ViewBag.CourseID = courseID.Value;
        // Lazy loading
        //viewModel.Enrollments = viewModel.Courses.Where(
        //    x => x.CourseID == courseID).Single().Enrollments;
        // Explicit loading
        var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
        db.Entry(selectedCourse).Collection(x => x.Enrollments).Load();
        foreach (Enrollment enrollment in selectedCourse.Enrollments)
        {
            db.Entry(enrollment).Reference(x => x.Student).Load();
        }

        viewModel.Enrollments = selectedCourse.Enrollments;
    }

    return View(viewModel);
}

선택한 Course 엔터티를 가져오면 새 코드는 해당 과정의 Enrollments 탐색 속성을 명시적으로 로드합니다.

db.Entry(selectedCourse).Collection(x => x.Enrollments).Load();

그런 다음 각 Enrollment 엔터티의 관련 Student 엔터티를 명시적으로 로드합니다.

db.Entry(enrollment).Reference(x => x.Student).Load();

메서드를 Collection 사용하여 컬렉션 속성을 로드하지만 엔터티가 하나만 있는 속성의 경우 메서드를 Reference 사용합니다.

지금 강사 인덱스 페이지를 실행하면 데이터가 검색되는 방법을 변경했지만 페이지에 표시되는 내용에 차이가 없습니다.

코드 가져오기

완료된 프로젝트 다운로드

추가 리소스

다른 Entity Framework 리소스에 대한 링크는 ASP.NET 데이터 액세스 - 권장 리소스에서 찾을 수 있습니다.

다음 단계

이 자습서에서는 다음과 같은 작업을 수행합니다.

  • 관련 데이터를 로드하는 방법 알아보기
  • 강좌 페이지 만들기
  • 강사 페이지 만들기

관련 데이터를 업데이트하는 방법을 알아보려면 다음 문서로 진행합니다.