Share via


ASP.NET MVC アプリケーションでの Entity Framework での関連データの読み取り (5/10)

著者: Tom Dykstra

Contoso University のサンプル Web アプリケーションでは、Entity Framework 5 Code First と Visual Studio 2012 を使用して ASP.NET MVC 4 アプリケーションを作成する方法を示します。 チュートリアル シリーズについては、シリーズの最初のチュートリアルを参照してください。

Note

解決できない問題が発生した場合は、完了した章をダウンロードして、問題を再現してみてください。 通常、完成したコードと自分のコードを比較することで、問題の解決策を見つけることができます。 一般的なエラーとその解決方法については、「エラーと回避策」をご覧ください。

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

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

Screenshot that shows the Contoso University Courses Index page.

Screenshot that shows the Contoso University Instructors Index page with an instructor and one of their courses selected.

Entity Framework がエンティティのナビゲーション プロパティに関連データを読み込むには、次の複数の方法があります。

  • 遅延読み込み。 エンティティが最初に読み込まれるときに、関連データは取得されません。 ただし、ナビゲーション プロパティに初めてアクセスしようとすると、そのナビゲーション プロパティに必要なデータが自動的に取得されます。 これにより、エンティティ自体に対して 1 つと、エンティティの関連データを取得するたびに 1 つのように、複数のクエリがデータベースに送信されます。

    Lazy_loading_example

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

    Eager_loading_example

  • 明示的読み込み。 これは遅延読み込みと似ていますが、関連データをコードで明示的に取得する点が異なります。コードの取得は、ナビゲーション プロパティにアクセスしても自動的には発生しません。 関連データを手動で読み込むには、エンティティのオブジェクト状態マネージャー エントリを取得し、コレクションの Collection.Load メソッドを呼び出すか、単一のエンティティを保持するプロパティの Reference.Load メソッドを呼び出します。 (次の例で、Administrator ナビゲーション プロパティを読み込む場合は、Collection(x => x.Courses)Reference(x => x.Administrator) に置き換えます)。

    Explicit_loading_example

プロパティ値はすぐには取得されないため、遅延読み込み (lazy loading) と明示的読み込み (explicit loading) はどちらも遅延読み込み (deferred loading) と呼ばれます。

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

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

通常は、遅延読み込みをオフにした場合にのみ、明示的読み込みを使用します。 遅延読み込みをオフにする必要があるシナリオの 1 つとして、シリアル化の実行中があります。 遅延読み込みとシリアル化はうまく組み合わせることができません。注意しないと、遅延読み込みが有効になっているときに意図したよりも大幅に多くのデータをクエリする結果になる可能性があります。 シリアル化は通常、ある型のインスタンスの各プロパティにアクセスすることによって機能します。 プロパティ アクセスによって遅延読み込みがトリガーされ、それらの遅延読み込みされたエンティティがシリアル化されます。 その後、シリアル化プロセスは遅延読み込みされたエンティティの各プロパティにアクセスするため、潜在的に遅延読み込みとシリアル化がさらに促進される可能性があります。 このような連鎖反応を防ぐには、エンティティをシリアル化する前に遅延読み込みをオフにします。

データベース コンテキスト クラスは、既定で遅延読み込みを実行します。 遅延読み込みを無効にするには、次の 2 つの方法があります。

  • 特定のナビゲーション プロパティの場合は、プロパティを宣言するときに virtual キーワードを省略します。

  • すべてのナビゲーション プロパティの場合は、LazyLoadingEnabledfalse に設定します。 たとえば、コンテキスト クラスのコンストラクターに次のコードを配置できます。

    this.Configuration.LazyLoadingEnabled = false;
    

遅延読み込みでは、パフォーマンスの問題を引き起こすコードをマスクできます。 たとえば、一括読み込みまたは明示的読み込みを指定せず、大量のエンティティを処理し、各イテレーションで複数のナビゲーション プロパティを使用するコードは、非常に非効率的な場合があります (データベースへのラウンド トリップが多いため)。 オンプレミスの SQL Server を使用した開発で優れたパフォーマンスを発揮するアプリケーションでは、待機時間の増加と遅延読み込みが原因で、Azure SQL Database に移動したときにパフォーマンスの問題が発生する可能性があります。 現実的なテスト負荷でデータベース クエリをプロファイリングすると、遅延読み込みが適切かどうかを判断するのに役立ちます。 詳細については、Entity Framework 戦略の解明: 関連データの読み込みの記事と、Entity Framework を使用した SQL Azure へのネットワーク待機時間の短縮の記事を参照してください。

部署名を表示する Courses インデックス ページを作成する

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

次の図に示すように、前に Student コントローラーに対して行ったのと同じオプションを使用して、Course エンティティ型の CourseController という名前を付けたコントローラーを作成します (ただし、イメージとは異なり、コンテキスト クラスは Models 名前空間ではなく DAL 名前空間にあります)。

Add_Controller_dialog_box_for_Course_controller

Controllers\CourseController.cs を開き、Index メソッドを確認します。

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

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

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

@model IEnumerable<ContosoUniversity.Models.Course>

@{
    ViewBag.Title = "Courses";
}

<h2>Courses</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th></th>
        <th>Number</th>
        <th>Title</th>
        <th>Credits</th>
        <th>Department</th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <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>
        <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>
    </tr>
}
</table>

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

  • 見出しが Index から Courses に変更されました。
  • 行リンクを左に移動しました。
  • 見出し Number の下に CourseID プロパティ値を示す列を追加しました。 (既定では、主キーは、通常、エンド ユーザーにとって意味がないため、スキャフォールディングされません。ただし、このケースでは、主キーは意味があり、表示する必要があります)。
  • 最後の列見出しを DepartmentID (Department エンティティへの外部キーの名前) から Department に変更しました。

最後の列のスキャフォールディングされたコードには、Department ナビゲーション プロパティに読み込まれる Department エンティティの Name プロパティが表示されます。

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

ページを実行して (Contoso University ホーム ページの [Courses] タブを選択)、部門名の一覧を表示します。

Courses_index_page_with_department_names

コースと登録を示す Instructors インデックス ページを作成する

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

Screenshot showing the Instructors Index page with an instructor and one of their courses selected.

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

  • インストラクターのリストには、OfficeAssignment エンティティからの関連データが表示されます。 Instructor エンティティと OfficeAssignment エンティティは、一対ゼロまたは一対一のリレーションシップです。 OfficeAssignment エンティティに一括読み込みを使用します。 前述のように、通常、一括読み込みは、主テーブルで取得したすべての行の関連データが必要なときにより効率的です。 このケースでは、割り当てられたすべてのインストラクターのオフィスの割り当てを表示する必要があります。
  • ユーザーがインストラクターを選択すると、関連する Course エンティティが表示されます。 Instructor エンティティと Course エンティティは多対多リレーションシップです。 Course エンティティとその関連 Department エンティティの一括読み込みを使用します。 このケースでは、選択したインストラクターのコースのみが必要なため、遅延読み込みの方が効率的な可能性があります。 ただし、この例では、ナビゲーション プロパティにあるエンティティ内のナビゲーション プロパティに一括読み込みを使用する方法を示します。
  • ユーザーがコースを選択すると、Enrollments エンティティ セットからの関連データが表示されます。 Course エンティティと Enrollment エンティティは一対多リレーションシップです。 Enrollment エンティティとその関連 Student エンティティの明示的読み込みを追加します。 (遅延読み込みが有効になっているため、明示的読み込みは必要ありませんが、ここでは明示的読み込みを行う方法を示しています)。

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

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

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; }
    }
}

選択した行のスタイルの追加

選択した行をマークするには、別の背景色が必要です。 この UI のスタイルを指定するには、次に示すように、Content\Site.css のセクション /* info and errors */ に次の強調表示されたコードを追加します。

/* info and errors */
.selectedrow 
{ 
    background-color: #a4d4e6; 
}
.message-info {
    border: 1px solid;
    clear: both;
    padding: 10px 20px;
}

Instructor コントローラーとビューの作成

次の図に示すように、InstructorController コントローラーを作成します。

Add_Controller_dialog_box_for_Instructor_controller

Controllers\InstructorController.cs を開き、ViewModels 名前空間の using ステートメントを追加します。

using ContosoUniversity.ViewModels;

Index メソッドのスキャフォールディングされたコードは、OfficeAssignment ナビゲーション プロパティに対してのみ一括読み込みを指定します。

public ViewResult 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.InstructorID == 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) を受け取り、必要なすべてのデータをビューに渡します。 パラメーターは、ページの Select ハイパーリンクによって指定されます。

ヒント

ルート データ

ルート データは、ルーティング テーブルで指定された URL セグメントでモデル バインダーが検出したデータです。 たとえば、既定のルートでは、controlleractionid のセグメントが指定されます。

routes.MapRoute(  
 name: "Default",  
 url: "{controller}/{action}/{id}",  
 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
);

次の URL では、既定のルートは Instructorcontroller として、Indexaction として、1 を id としてマッピングします。これらは、ルート データ値です。

http://localhost:1230/Instructor/Index/1?courseID=2021

"?courseID=2021" はクエリ文字列値です。 モデル バインダーは、id をクエリ文字列値として渡す場合にも機能します。

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

URL は Razor ビューの ActionLink ステートメントによって作成されます。 次のコードでは、id パラメーターが既定のルートと一致するため、id がルート データに追加されます。

@Html.ActionLink("Select", "Index", new { id = item.PersonID  })

次のコードでは、courseID は既定ルートのパラメーターと一致しないため、クエリ文字列として追加されます。

@Html.ActionLink("Select", "Index", new { courseID = item.CourseID })

このコードは、ビュー モデルのインスタンスを作成し、インストラクターのリストに配置することから始めます。 コードでは、Instructor.OfficeAssignmentInstructor.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);

2 番目の Include メソッドは Courses を読み込み、読み込まれる各コースで Course.Department ナビゲーション プロパティの一括読み込みを行います。

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

前述のように、一括読み込みは必須ではありませんが、パフォーマンスを向上させるために行われます。 ビューには常に OfficeAssignment エンティティが必要なため、同じクエリでフェッチする方が効率的です。 インストラクターが Web ページで選択されたときに、Course エンティティが必要なため、一括読み込みが遅延読み込みよりも適しているのは、ページがコースが選択された状態で表示されることが、選択されない状態よりも多い場合のみです。

インストラクター ID が選択されている場合、選択したインストラクターはビュー モデルのインストラクターの一覧から取得されます。 次に、ビュー モデルの Courses プロパティが Course エンティティと共にそのインストラクターの Courses ナビゲーション プロパティから読み込まれます。

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

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

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

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

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

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

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

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

Instructor インデックス ビューの変更

Views\Instructor\Index.cshtml で、既存のコードを次のコードに置き換えます。 変更が強調表示されています。

@model ContosoUniversity.ViewModels.InstructorIndexData

@{
    ViewBag.Title = "Instructors";
}

<h2>Instructors</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table> 
    <tr> 
        <th></th> 
        <th>Last Name</th> 
        <th>First Name</th> 
        <th>Hire Date</th> 
        <th>Office</th>
    </tr> 
    @foreach (var item in Model.Instructors) 
    { 
        string selectedRow = ""; 
        if (item.InstructorID == ViewBag.InstructorID) 
        { 
            selectedRow = "selectedrow"; 
        } 
        <tr class="@selectedRow" valign="top"> 
            <td> 
                @Html.ActionLink("Select", "Index", new { id = item.InstructorID }) | 
                @Html.ActionLink("Edit", "Edit", new { id = item.InstructorID }) | 
                @Html.ActionLink("Details", "Details", new { id = item.InstructorID }) | 
                @Html.ActionLink("Delete", "Delete", new { id = item.InstructorID }) 
            </td> 
            <td> 
                @item.LastName 
            </td> 
            <td> 
                @item.FirstMidName 
            </td> 
            <td> 
                @Html.DisplayFor(modelItem => item.HireDate)
            </td> 
            <td> 
                @if (item.OfficeAssignment != null) 
                { 
                    @item.OfficeAssignment.Location  
                } 
            </td> 
        </tr> 
    } 
</table>

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

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

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

  • 行リンク列を左に移動しました。

  • FullName 列を削除しました。

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

    <td> 
        @if (item.OfficeAssignment != null) 
        { 
            @item.OfficeAssignment.Location  
        } 
    </td>
    
  • 選択したインストラクターの tr 要素に class="selectedrow" を動的に追加するコードを追加しました。 これにより、先ほど作成した CSS クラスを使用して、選択した行の背景色が設定されます。 (valign 属性は、次のチュートリアルで複数行の列をテーブルに追加するときに役立ちます)。

    string selectedRow = ""; 
    if (item.InstructorID == ViewBag.InstructorID) 
    { 
        selectedRow = "selectedrow"; 
    } 
    <tr class="@selectedRow" valign="top">
    
  • 各行の他のリンクの直前に Select というラベルの新しい ActionLink を追加しました。これは、選択されたインストラクターの ID が Index メソッドに送信されるようにします。

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

Instructors_index_page_with_nothing_selected

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

<td> 
                @if (item.OfficeAssignment != null) 
                { 
                    @item.OfficeAssignment.Location  
                } 
            </td> 
        </tr> 
    } 
</table>

@if (Model.Courses != null) 
{ 
    <h3>Courses Taught by Selected Instructor</h3> 
<table> 
    <tr> 
        <th></th> 
        <th>ID</th> 
        <th>Title</th> 
        <th>Department</th> 
    </tr> 
 
    @foreach (var item in Model.Courses) 
    { 
        string selectedRow = ""; 
        if (item.CourseID == ViewBag.CourseID) 
        { 
            selectedRow = "selectedrow"; 
        } 
    <tr class="@selectedRow"> 
        <td> 
            @Html.ActionLink("Select", "Index", new { courseID = item.CourseID }) 
        </td> 
        <td> 
            @item.CourseID 
        </td> 
        <td> 
            @item.Title 
        </td> 
        <td> 
            @item.Department.Name 
        </td> 
    </tr> 
    } 
 
</table> 
}

このコードでは、ビュー モデルの Courses プロパティを読み取り、コースのリストを表示します。 また、選択したコースの ID を Index アクション メソッドに送信する、Select ハイパーリンクも指定します。

Note

.css ファイルはブラウザーによってキャッシュされます。 アプリケーションの実行時に変更が表示されない場合は、ハード更新を行います (Ctrl キーを押しながら [更新] ボタンをクリックするか、Ctrl キーを押しながら F5 キーを押します)。

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

Instructors_index_page_with_instructor_selected

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

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

このコードでは、コースに登録された受講生のリストを表示するために、ビュー モデルの Enrollments プロパティを読み取ります。

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

Screenshot of the Instructors Index page with an instructor and one of their courses selected.

明示的読み込みの追加

InstructorController.cs を開き、Index メソッドが選択したコースの登録の一覧を取得する方法を確認します。

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

インストラクターの一覧を取得したときに、Courses ナビゲーション プロパティと各コースの Department プロパティに対して一括読み込みを指定しました。 次に、Courses コレクションをビュー モデルに配置しました。ここでは、そのコレクション内の 1 つのエンティティから 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.InstructorID == id.Value).Single().Courses;
    }
    
    if (courseID != null)
    {
        ViewBag.CourseID = courseID.Value;
        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 メソッドを使用してコレクション プロパティを読み込みますが、1 つのエンティティのみを保持するプロパティの場合は、Reference メソッドを使用します。 Instructor インデックス ページを実行すると、データを取得する方法を変更しているにもかかわらず、ページ上で表示される内容に変わりがないことがわかります。

まとめ

3 つの方法 (遅延、一括、明示的) をすべて使用して、関連するデータがナビゲーション プロパティに読み込まれるようになりました。 次のチュートリアルでは、関連データの更新方法を学習します。

他の Entity Framework リソースへのリンクは、ASP.NET データ アクセス コンテンツ マップに関するページにあります。