チュートリアル: 関連データを読み取る - ASP.NET MVC と EF Core

前のチュートリアルでは、School データ モデルを作成しました。 このチュートリアルでは、関連データ (Entity Framework がナビゲーション プロパティに読み込むデータ) の読み取りと表示を行います。

以下の図は、使用するページを示しています。

Courses Index page

Instructors Index page

このチュートリアルでは、次の作業を行いました。

  • 関連データを読み込む方法を学習する
  • Courses ページを作成する
  • Instructors ページを作成する
  • 明示的読み込みについて学習する

必須コンポーネント

オブジェクト リレーショナル マッピング (ORM) ソフトウェア (Entity Framework など) では、次のように関連データをエンティティのナビゲーション プロパティに読み込むことができる方法がいくつかあります。

  • 一括読み込み: エンティティが読み取られるときに、関連データがエンティティと共に取得されます。 これは通常、必要なデータをすべて取得する 1 つの結合クエリになります。 Include メソッドと ThenInclude メソッドを使用して、Entity Framework Core で一括読み込みを指定します。

    Eager loading example

    分離したクエリのデータの一部を取得することができ、EF によってナビゲーション プロパティが "修正されます"。 つまり、EF で、前に取得したエンティティのナビゲーション プロパティに属する、個別に取得したエンティティを自動的に追加します。 関連データを取得するクエリの場合、リストやオブジェクトを返すメソッド (ToListSingle など) の代わりに、Load メソッドを使用できます。

    Separate queries example

  • 明示的読み込み: エンティティが最初に読み込まれるときに、関連データは取得されません。 必要な場合に、関連データを取得するコードを記述します。 分離したクエリによる一括読み込みのように、明示的読み込みでは、複数のクエリがデータベースに送信されます。 明示的読み込みを使用すると、コードで読み込まれるナビゲーション プロパティを指定する点が異なります。 Entity Framework Core 1.1 では、Load メソッドを使用して、明示的読み込みを実行できます。 次に例を示します。

    Explicit loading example

  • 遅延読み込み: エンティティが最初に読み込まれるときに、関連データは取得されません。 ただし、ナビゲーション プロパティに初めてアクセスしようとすると、そのナビゲーション プロパティに必要なデータが自動的に取得されます。 初めてナビゲーション プロパティからデータを取得しようとするたびに、クエリがデータベースに送信されます。 Entity Framework Core 1.0 では、遅延読み込みはサポートされません。

パフォーマンスに関する考慮事項

取得したすべてのエンティティの関連データが必要な場合は、通常、データベースに送信された 1 つのクエリの方が、取得した各エンティティに対する分離したクエリよりも効率的なため、一括読み込みを使用すると、より最適なパフォーマンスが得られます。 たとえば、各部門に関連コースが 10 個あるとします。 すべての関連データの一括読み込みは、1 つの (結合) クエリと 1 回のデータベースとのラウンド トリップだけになります。 部門ごとのコースへの分離したクエリでは、データベースとのラウンド トリップが 11 回行われることになります。 データベースとの余分なラウンド トリップは、特に待ち時間が長いときのパフォーマンスに悪影響をもたらします。

その一方で、一部のシナリオでは、分離したクエリがより効率的です。 1 つのクエリ内のすべての関連データの一括読み込みでは、SQL Server で効率的に処理できない、複雑な結合が生成される場合があります。 または、処理しているエンティティのセットのサブセットのためだけにエンティティのナビゲーション プロパティにアクセスする必要がある場合、事前のすべてを取得する一括読み込みでは、必要以上にデータを取得するため、分離したクエリの方が適切に実行される可能性があります。 パフォーマンスが重要な場合、最適な選択を行うために、両方の方法でパフォーマンスをテストすることをお勧めします。

Courses ページを作成する

Course エンティティには、コースが割り当てられている部門の Department エンティティを含む、ナビゲーション プロパティが含まれます。 コースのリストに割り当てられた部門の名前を表示するには、Course.Department ナビゲーション プロパティにある Department エンティティから Name プロパティを取得する必要があります。

次の図に示すように、StudentsController に対して前に実行した Entity Framework のスキャフォールディング機能を使用して、ビューと共に MVC コントローラーに同じオプションを使用して、Course エンティティ型の CoursesController という名前のコントローラーを作成します。

Add Courses controller

CoursesController.cs を開き、Index メソッドを調べます。 自動スキャフォールディングでは、Include メソッドを使って、Department ナビゲーション プロパティに一括読み込みを指定しています。

Index メソッドを、Course エンティティ (schoolContext の代わりに courses) を返す IQueryable により適切な名前を使用する次のコードに置き換えます。

public async Task<IActionResult> Index()
{
    var courses = _context.Courses
        .Include(c => c.Department)
        .AsNoTracking();
    return View(await courses.ToListAsync());
}

Views/Courses/Index.cshtml を開き、テンプレート コードを次のコードに置き換えます。 変更が強調表示されています。

@model IEnumerable<ContosoUniversity.Models.Course>

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

<h2>Courses</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.CourseID)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Credits)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Department)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @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>
                    <a asp-action="Edit" asp-route-id="@item.CourseID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.CourseID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

スキャフォールディング コードに、次の変更を行いました。

  • 見出しが Index から Courses に変更されました。

  • CourseID プロパティ値を示す Number 列が追加されました。 既定では、主キーは、通常、エンド ユーザーにとって意味がないため、スキャフォールディングされません。 ただし、このケースでは、主キーは意味があり、表示する必要があります。

  • 部門名が表示されるように、Department 列を変更しました。 コードは、Department ナビゲーション プロパティに読み込まれる Department エンティティの Name プロパティを表示します。

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

アプリを実行し、 [Courses] タブを選択して部門名のリストを表示します。

Courses Index page

Instructors ページを作成する

このセクションでは、Instructors ページを表示するために、Instructor エンティティのコントローラーとビューを作成します。

Instructors Index page

このページは、次の方法で関連データを読み取って表示します。

  • インストラクターのリストには、OfficeAssignment エンティティからの関連データが表示されます。 Instructor エンティティと OfficeAssignment エンティティは、一対ゼロまたは一対一のリレーションシップです。 OfficeAssignment エンティティに一括読み込みを使用します。 前述のように、通常、一括読み込みは、主テーブルで取得したすべての行の関連データが必要なときにより効率的です。 このケースでは、割り当てられたすべてのインストラクターのオフィスの割り当てを表示する必要があります。

  • ユーザーがインストラクターを選択すると、関連する Course エンティティが表示されます。 Instructor エンティティと Course エンティティは多対多リレーションシップです。 Course エンティティとその関連 Department エンティティの一括読み込みを使用します。 このケースでは、選択したインストラクターのコースのみが必要なため、分離したクエリの方が効率的な可能性があります。 ただし、この例では、ナビゲーション プロパティにあるエンティティ内のナビゲーション プロパティに一括読み込みを使用する方法を示します。

  • ユーザーがコースを選択すると、Enrollments エンティティ セットからの関連データが表示されます。 Course エンティティと Enrollment エンティティは一対多リレーションシップです。 Enrollment エンティティとそれに関連する Student エンティティに分離したクエリを使用します。

Instructor インデックス ビューのビュー モデルを作成する

Instructors ページには、3 つの異なるテーブルからデータが表示されます。 そのため、テーブルの 1 つにデータを保持するごとに、3 つのプロパティを含むビュー モデルを作成します。

SchoolViewModels フォルダー内に InstructorIndexData.cs を作成し、既存のコードを次のコードで置き換えます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

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

Instructor コントローラーとビューを作成する

次の図に示すように、EF の読み取り/書き込みアクションで、Instructors コントローラーを作成します。

Add Instructors controller

InstructorsController.cs を開いて、ViewModels 名前空間に対する using ステートメントを追加します。

using ContosoUniversity.Models.SchoolViewModels;

Index メソッドを次のコードに置き換えて、関連データの一括読み込みを行い、ビュー モデルに配置します。

public async Task<IActionResult> Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();
    viewModel.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Enrollments)
                    .ThenInclude(i => i.Student)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
          .AsNoTracking()
          .OrderBy(i => i.LastName)
          .ToListAsync();
    
    if (id != null)
    {
        ViewData["InstructorID"] = id.Value;
        Instructor instructor = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single();
        viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
    }

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

    return View(viewModel);
}

メソッドでは、選択したインストラクターと選択したコースの ID 値を指定する、オプションのルート データ (id) とクエリ文字列パラメーター (courseID) を受け入れます。 パラメーターは、ページの Select ハイパーリンクによって指定されます。

このコードは、ビュー モデルのインスタンスを作成し、インストラクターのリストに配置することから始めます。 コードでは、Instructor.OfficeAssignmentInstructor.CourseAssignments ナビゲーション プロパティに一括読み込みを指定します。 CourseAssignments プロパティ内で Course プロパティが読み込まれ、そのプロパティ内で EnrollmentsDepartment プロパティが読み込まれ、各 Enrollment エンティティ内で Student プロパティが読み込まれます。

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

ビューには常に OfficeAssignment エンティティが必要なため、同じクエリでフェッチする方が効率的です。 インストラクターが Web ページで選択されたときに、Course エンティティが必要なため、1 つのクエリが複数のクエリよりも適しているのは、ページに選択したコースを含めないよりも、含めて表示することの方が多い場合のみです。

Course から 2 つのプロパティが必要なため、コードでは CourseAssignmentsCourse を繰り返します。 ThenInclude 呼び出しの最初の文字列では、CourseAssignment.CourseCourse.Enrollments、および Enrollment.Student を取得します。

関連データの複数のレベルを含める方法の詳細については、こちらを参照してください。

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

コードのその時点で、もう 1 つの ThenInclude は、必要としない Student のナビゲーション プロパティ用になります。 ただし、Include を呼び出すと、Instructor プロパティを使ってやり直されるため、もう一度チェーンを順に移動する必要があります。今回は Course.Enrollments の代わりに Course.Department を指定しています。

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

次のコードは、インストラクターが選択されたときに実行されます。 選択されたインストラクターがビュー モデルのインストラクターのリストから取得されます。 次に、ビュー モデルの Courses プロパティが Course エンティティと共にそのインストラクターの CourseAssignments ナビゲーション プロパティから読み込まれます。

if (id != null)
{
    ViewData["InstructorID"] = id.Value;
    Instructor instructor = viewModel.Instructors.Where(
        i => i.ID == id.Value).Single();
    viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

Where メソッドはコレクションを返しますが、このケースでは、そのメソッドに渡された条件は、返されている Instructor エンティティの 1 つのみになります。 Single メソッドを実行すると、コレクションが、エンティティの CourseAssignments プロパティへのアクセス権を付与する 1 つの Instructor エンティティに変換されます。 CourseAssignments プロパティには、関連する Course エンティティのみを必要とする、CourseAssignment エンティティが含まれます。

コレクションに項目が 1 つのみであることがわかっている場合、コレクションで Single メソッドを使用します。 コレクションが空になる場合、または複数の項目がある場合、Single メソッドから例外がスローされます。 代わりに、コレクションが空の場合に既定値 (この場合は null) を返す SingleOrDefault を使用します。 ただし、(null 参照で Courses プロパティを見つけようとして) 引き続き例外となる場合は、例外メッセージでは問題の原因があまり明確に示されません。 Single メソッドを呼び出す場合、個別に Where メソッドを呼び出す代わりに、Where 条件で渡すこともできます。

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

これは次のコードの代わりに使用します。

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

次に、コースが選択された場合、選択したコースはビュー モデルのコースのリストから取得されます。 次に、ビュー モデルの Enrollments プロパティが Enrollment エンティティと共にそのコースの Enrollments ナビゲーション プロパティから読み込まれます。

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

追跡と追跡なし

追跡なしのクエリは、読み取り専用のシナリオで結果が使用される場合に役立ちます。 変更追跡情報を設定する必要がないため、通常は実行が速くなります。 データベースから取得したエンティティを更新する必要がない場合は、追跡クエリよりも追跡なしのクエリの方がパフォーマンスが向上する可能性があります。

場合によっては、追跡クエリは追跡なしのクエリよりも効率的です。 詳細については、「追跡と追跡なしのクエリ」を参照してください。

Instructor インデックス ビューを変更する

Views/Instructors/Index.cshtml で、テンプレート コードを次のコードに置き換えます。 変更が強調表示されています。

@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

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

<h2>Instructors</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>Last Name</th>
            <th>First Name</th>
            <th>Hire Date</th>
            <th>Office</th>
            <th>Courses</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Instructors)
        {
            string selectedRow = "";
            if (item.ID == (int?)ViewData["InstructorID"])
            {
                selectedRow = "table-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>
                    @foreach (var course in item.CourseAssignments)
                    {
                        @course.Course.CourseID @course.Course.Title <br />
                    }
                </td>
                <td>
                    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
           }
    </tbody>
</table>
@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

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

<h2>Instructors</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>Last Name</th>
            <th>First Name</th>
            <th>Hire Date</th>
            <th>Office</th>
            <th>Courses</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Instructors)
        {
            string selectedRow = "";
            if (item.ID == (int?)ViewData["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>
                    @foreach (var course in item.CourseAssignments)
                    {
                        @course.Course.CourseID @course.Course.Title <br />
                    }
                </td>
                <td>
                    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
           }
    </tbody>
</table>

既存のコードに次の変更を行いました。

  • モデル クラスが InstructorIndexData に変更されました。

  • Index のページ タイトルが Instructors に変更されました。

  • item.OfficeAssignment が null ではない場合にのみ item.OfficeAssignment.Location を表示する Office 列を追加しました。 (これは、一対ゼロまたは一対一のリレーションシップであるため、関連する OfficeAssignment エンティティがない場合があります。)

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • インストラクターごとに担当したコースを表示する Courses 列を追加しました。 詳細については、Razor 構文記事の「明示的な行の遷移」セクションを参照してください。

  • 選択したインストラクターの tr 要素にブートストラップ CSS クラスを条件付きで追加するコードを追加しました。 このクラスにより、選択した行の背景色が設定されます。

  • Index メソッドに送信される選択されたインストラクターの ID を発生させる、各行の他のリンクの直前に Select というラベルの新しいハイパーリンクを追加しました。

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    

アプリを実行し、 [Instructors] タブを選択します。関連する OfficeAssignment エンティティがない場合は、ページに関連する OfficeAssignment エンティティの Location プロパティと空の表のセルが表示されます。

Instructors Index page nothing selected

Views/Instructors/Index.cshtml ファイルでは、テーブル要素を閉じた後に (ファイルの終わりに)、次のコードを追加します。 このコードでは、インストラクターが選択されたときに、インストラクターに関連するコースのリストを表示します。


@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 == (int?)ViewData["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 ハイパーリンクも指定します。

ページを更新し、インストラクターを選択します。 選択したインストラクターに割り当てられたコースを表示するグリッドを表示し、各コースに割り当てられた部門の名前を表示します。

Instructors Index page instructor selected

追加したコード ブロックの後に、次のコードを追加します。 このコードは、コースを選択したときに、コースに登録されている受講者のリストを表示します。

@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 プロパティを読み取ります。

もう一度ページを更新し、インストラクターを選択します。 次に、コースを選択して、登録済みの受講者とその成績のリストを表示します。

Instructors Index page instructor and course selected

明示的読み込みについて

InstructorsController.cs でインストラクターの一覧を取得したときに、CourseAssignments ナビゲーション プロパティに一括読み込みを指定しました。

ユーザーは選択したインストラクターとコースの登録内容をほとんど表示する必要がないとします。 その場合は、要求された場合にのみ、登録データを読み取る必要がある可能性があります。 明示的読み込みを行う方法の例を表示するには、Index メソッドを次のコードに置き換えます。このコードでは、Enrollments の一括読み込みを削除して、そのプロパティを明示的に読み込みます。 コードの変更が強調表示されています。

public async Task<IActionResult> Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();
    viewModel.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
          .OrderBy(i => i.LastName)
          .ToListAsync();

    if (id != null)
    {
        ViewData["InstructorID"] = id.Value;
        Instructor instructor = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single();
        viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
    }

    if (courseID != null)
    {
        ViewData["CourseID"] = courseID.Value;
        var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
        await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
        foreach (Enrollment enrollment in selectedCourse.Enrollments)
        {
            await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
        }
        viewModel.Enrollments = selectedCourse.Enrollments;
    }

    return View(viewModel);
}

新しいコードでは、インストラクター エンティティを取得するコードから登録データを呼び出す ThenInclude メソッドを削除します。 AsNoTracking も削除されます。 インストラクターとコースが選択された場合、強調表示されたコードによって、選択されたコードの Enrollment エンティティ、および各 EnrollmentStudent エンティティが取得されます。

アプリを実行して、Instructors/Index ページに移動すると、データを取得する方法を変更しているにもかかわらず、ページ上で表示される内容に変わりがないことがわかります。

コードを取得する

完成したアプリケーションをダウンロードまたは表示する。

次の手順

このチュートリアルでは、次の作業を行いました。

  • 関連データを読み込む方法を学習した
  • Courses ページを作成した
  • Instructors ページを作成した
  • 明示的読み込みについて学習した

関連データを更新する方法について学習するには、次のチュートリアルに進んでください。