Teil 6: Razor-Seiten mit EF Core in ASP.NET Core – Lesen verwandter Daten

Von Tom Dykstra, Jon P. Smith und Rick Anderson

Die Web-App Contoso University veranschaulicht, wie Razor-Seiten-Web-Apps mit EF Core und Visual Studio erstellt werden können. Informationen zu den Tutorials finden Sie im ersten Tutorial.

Wenn Probleme auftreten, die Sie nicht beheben können, laden Sie die vollständige App herunter, und vergleichen Sie diesen Code mit dem Code, den Sie anhand des Tutorials erstellt haben.

Dieses Tutorial zeigt, wie verwandte Daten gelesen und angezeigt werden. Verwandte Daten sind Daten, die von EF Core in die Navigationseigenschaften geladen werden.

Die folgenden Abbildungen zeigen die abgeschlossenen Seiten für dieses Tutorial:

Courses Index page

Instructors Index page

Explizites Laden, Eager Loading und Lazy Loading

Es gibt mehrere Möglichkeiten, wie EF Core verwandte Daten in die Navigationseigenschaften einer Entität laden kann:

  • Eager Loading (vorzeitiges Laden). Beim Eager Loading lädt eine Abfrage für einen Entitätstyp auch verwandte Entitäten. Wenn eine Entität gelesen wird, werden ihre zugehörigen Daten abgerufen. Dies führt normalerweise zu einer einzelnen Joinabfrage, die alle Daten abruft, die erforderlich sind. EF Core wird mehrere Abfragen für einige Typen von Eager Loading ausgeben. Das Ausgeben mehrerer Abfragen kann effizienter sein als eine einzelne große Abfrage. Eager Loading wird mit den Include- und ThenInclude-Methoden angegeben.

    Eager loading example

    Eager Loading sendet mehrere Abfragen, wenn eine Sammlungsnavigation enthalten ist:

    • Eine Abfrage für die Hauptabfrage
    • Eine Abfrage für jeden „Sammlungsrand“ in der Ladestruktur.
  • Separate Abfragen mit Load: Die Daten können in separaten Abfragen abgerufen werden und EF Core korrigiert die Navigationseigenschaften. „Korrigieren“ bedeutet, dass EF Core die Navigationseigenschaften automatisch mit Daten füllt. Separate Abfragen mit Load ähneln mehr dem expliziten Laden als dem Eager Loading.

    Separate queries example

    Hinweis:EF Core korrigiert automatisch Navigationseigenschaften für alle anderen Entitäten, die zuvor in die Kontextinstanz geladen wurden. Auch wenn die Daten für eine Navigationseigenschaft nicht explizit eingeschlossen sind, kann die Eigenschaft immer noch aufgefüllt werden, wenn einige oder alle verwandten Entitäten zuvor geladen wurden.

  • Explizites Laden. Wenn die Entität zuerst gelesen wird, werden verwandte Daten nicht abgerufen. Es muss Code geschrieben werden, um die verwandten Daten bei Bedarf abzurufen. Explizites Laden mit separaten Abfragen führt zu mehreren Abfragen, die an die Datenbank gesendet werden. Mit explizitem Laden gibt der Code die zu ladenden Navigationseigenschaften an. Verwenden Sie für explizites Laden die Load-Methode. Beispiel:

    Explicit loading example

  • Lazy Loading (verzögertes Laden). Wenn die Entität zuerst gelesen wird, werden verwandte Daten nicht abgerufen. Wenn zum ersten Mal auf eine Navigationseigenschaft zugegriffen wird, werden die für diese Navigationseigenschaft erforderlichen Daten automatisch abgerufen. Wenn zum ersten Mal auf eine Navigationseigenschaft zugegriffen wird, wird jedes Mal eine Abfrage an die Datenbank gesendet. Lazy Loading kann die Leistung beeinträchtigen, zum Beispiel wenn Entwickler N+1-Abfragen verwenden. N+1-Abfragen laden ein übergeordnetes Element und zählen über untergeordnete Elemente auf.

Erstellen von Course-Seiten

Die Course-Entität enthält eine Navigationseigenschaft, die die zugehörige Department-Entität enthält.

Course.Department

So zeigen Sie den Namen des zugewiesenen Fachbereichs für einen Kurs an:

  • Laden Sie die zugehörige Department-Entität in die Course.Department-Navigationseigenschaft.
  • Rufen Sie den Namen aus der Name-Eigenschaft der Department-Entität ab.

Gerüstbau von Course-Seiten

  • Befolgen Sie die Anweisungen unter Gerüstbau der Student-Seiten mit den folgenden Ausnahmen:

    • Erstellen Sie einen Ordner Pages/Courses.
    • Verwenden Sie Course als Modellklasse.
    • Verwenden Sie die vorhandene Kontextklasse, anstatt eine neue Klasse zu erstellen.
  • Öffnen Sie Pages/Courses/Index.cshtml.cs, und untersuchen Sie die OnGetAsync-Methode. Die Gerüstbauengine gibt Eager Loading für die Department-Navigationseigenschaft an. Die Include-Methode gibt Eager Loading an.

  • Führen Sie die Anwendung aus, und klicken Sie auf den Link Kurse. Die Abteilungsspalte zeigt die DepartmentID an, die nicht hilfreich ist.

Anzeigen des Fachbereichnamens

Aktualisieren Sie „Pages/Courses/Index.cshtml.cs“ mit dem folgenden Code:

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

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

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

        public IList<Course> Courses { get; set; }

        public async Task OnGetAsync()
        {
            Courses = await _context.Courses
                .Include(c => c.Department)
                .AsNoTracking()
                .ToListAsync();
        }
    }
}

Der vorstehende Code ändert die Course-Eigenschaft in Courses und fügt AsNoTracking hinzu.

Abfragen ohne Nachverfolgung sind nützlich, wenn die Ergebnisse in einem schreibgeschützten Szenario verwendet werden. Sie werden in der Regel schneller ausgeführt, da keine Informationen für die Änderungsnachverfolgung eingerichtet werden müssen. Wenn die aus der Datenbank abgerufenen Entitäten nicht aktualisiert werden müssen, ist eine Abfrage ohne Nachverfolgung wahrscheinlich besser als eine Abfrage mit Nachverfolgung.

In einigen Fällen ist eine Abfrage mit Nachverfolgung effizienter als eine Abfrage ohne Nachverfolgung. Weitere Informationen finden Sie unter Abfragen mit Nachverfolgung und Abfragen ohne Nachverfolgung im Vergleich. Im vorherigen Code wird AsNoTracking aufgerufen, da die Entitäten nicht im aktuellen Kontext aktualisiert werden.

Aktualisieren Sie Pages/Courses/Index.cshtml mit folgendem Code.

@page
@model ContosoUniversity.Pages.Courses.IndexModel

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

<h1>Courses</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Courses[0].CourseID)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Courses[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Courses[0].Credits)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Courses[0].Department)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model.Courses)
{
        <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-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Die folgenden Änderungen wurden am Codegerüst vorgenommen:

  • Der Course-Eigenschaftenname wurde in Courses geändert.

  • Die Spalte Anzahl wurde hinzugefügt. Sie zeigt den CourseID-Eigenschaftswert an. Primärschlüssel werden nicht standardmäßig eingerüstet, da sie normalerweise ohne Bedeutung für Endbenutzer sind. Allerdings hat der Primärschlüssel in diesem Fall jedoch Bedeutung.

  • Die Spalte Abteilung wurde geändert, sodass sie jetzt den Namen der Abteilung anzeigt. Der Code zeigt die Name-Eigenschaft der Department-Entität an, die in die Department-Navigationseigenschaft geladen wird:

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

Führen Sie die Anwendung aus. Wählen Sie die Registerkarte Kurse aus, um die Liste mit den Abteilungsnamen anzuzeigen.

Courses Index page

Die OnGetAsync-Methode lädt zugehörige Daten mit der Include-Methode. Die Select-Methode ist eine Alternative, die nur die zugehörigen benötigten Daten lädt. Für einzelne Elemente wie Department.Name wird ein SQL INNER JOIN verwendet. Für Sammlungen wird ein anderer Zugriff auf die Datenbank verwendet, aber Gleiches gilt für den Include-Operator für Sammlungen.

Der folgende Code lädt verwandte Daten mit der Select-Methode:

public IList<CourseViewModel> CourseVM { get; set; }

public async Task OnGetAsync()
{
    CourseVM = await _context.Courses
    .Select(p => new CourseViewModel
    {
        CourseID = p.CourseID,
        Title = p.Title,
        Credits = p.Credits,
        DepartmentName = p.Department.Name
    }).ToListAsync();
}

Der voranstehende Code gibt keine Entitätstypen zurück, weshalb keine Nachverfolgung ausgeführt wird. Weitere Informationen zur EF-Nachverfolgung finden Sie unter Nachverfolgungsabfragen im Vergleich zu Abfragen ohne Nachverfolgung.

Die CourseViewModel:

public class CourseViewModel
{
    public int CourseID { get; set; }
    public string Title { get; set; }
    public int Credits { get; set; }
    public string DepartmentName { get; set; }
}

Die vollständigen Razor-Seiten finden Sie unter IndexSelectModel.

Erstellen von Instructor-Seiten

In diesem Abschnitt erfolgt der Gerüstbau von Instructor-Seiten, und der Index-Seite des Dozenten werden zugehörige Courses und Enrollments hinzugefügt.

Instructors Index page

Auf dieser Seite werden verwandte Daten auf folgende Weise gelesen und angezeigt:

  • Die Liste der Dozenten zeigt verwandte Daten aus der OfficeAssignment-Entität (Office in der vorherigen Abbildung). Die Instructor- und OfficeAssignment-Entitäten stehen in einer 1:0..1-Beziehung zueinander. Eager Loading wird für die OfficeAssignment-Entitäten verwendet. Eager Loading ist in der Regel effizienter, wenn die verwandten Daten angezeigt werden müssen. In diesem Fall werden die Office-Zuweisungen für die Dozenten angezeigt.
  • Wenn der Benutzer einen Dozenten auswählt, werden zugehörige Course-Entitäten angezeigt. Die Instructor- und Course-Entitäten stehen in einer m:n-Beziehung zueinander. Für die Course-Entitäten und ihre verwandten Department-Entitäten wird das Eager Loading verwendet. In diesem Fall können separate Abfragen effizienter sein, da nur Kurse für den ausgewählten Dozenten benötigt werden. Dieses Beispiel zeigt, wie Eager Loading für Navigationseigenschaften in Entitäten in den Navigationseigenschaften verwendet wird.
  • Wenn der Benutzer einen Kurs auswählt, werden zugehörige Daten aus der Enrollments-Entität angezeigt. In der vorherigen Abbildung sind der Name des Studenten und die Note angezeigt. Die Course- und Enrollment-Entitäten stehen in einer 1:n-Beziehung zueinander.

Erstellen von Anzeigemodellen

Die Dozentenseite zeigt Daten aus drei verschiedenen Tabellen. Ein Ansichtsmodell ist erforderlich, das drei Eigenschaften enthält, die die drei Tabellen darstellen.

Erstellen Sie Models/SchoolViewModels/InstructorIndexData.cs mit dem folgenden Code:

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

Gerüstbau von Instructor-Seiten

  • Befolgen Sie die Anweisungen unter Gerüstbau der Student-Seiten mit den folgenden Ausnahmen:

    • Erstellen Sie einen Ordner Pages/Instructors.
    • Verwenden Sie Instructor als Modellklasse.
    • Verwenden Sie die vorhandene Kontextklasse, anstatt eine neue Klasse zu erstellen.

Führen Sie die App aus, und navigieren Sie zur Dozentenseite.

Aktualisieren Sie Pages/Instructors/Index.cshtml.cs mit folgendem Code:

using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;  // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

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

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

        public InstructorIndexData InstructorData { get; set; }
        public int InstructorID { get; set; }
        public int CourseID { get; set; }

        public async Task OnGetAsync(int? id, int? courseID)
        {
            InstructorData = new InstructorIndexData();
            InstructorData.Instructors = await _context.Instructors
                .Include(i => i.OfficeAssignment)                 
                .Include(i => i.Courses)
                    .ThenInclude(c => c.Department)
                .OrderBy(i => i.LastName)
                .ToListAsync();

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

            if (courseID != null)
            {
                CourseID = courseID.Value;
                IEnumerable<Enrollment> Enrollments = await _context.Enrollments
                    .Where(x => x.CourseID == CourseID)                    
                    .Include(i=>i.Student)
                    .ToListAsync();                 
                InstructorData.Enrollments = Enrollments;
            }
        }
    }
}

Die OnGetAsync-Methode akzeptiert optional Routendaten für die ID des ausgewählten Dozenten.

Untersuchen Sie die Abfrage in der Datei Pages/Instructors/Index.cshtml.cs:

InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
    .Include(i => i.OfficeAssignment)                 
    .Include(i => i.Courses)
        .ThenInclude(c => c.Department)
    .OrderBy(i => i.LastName)
    .ToListAsync();

Der Code gibt Eager Loading für die folgenden Navigationseigenschaften an:

  • Instructor.OfficeAssignment
  • Instructor.Courses
    • Course.Department

Der folgende Code wird ausgeführt, wenn ein Dozent ausgewählt wird (also id != null).

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

Der ausgewählte Dozent wird aus der Liste der Dozenten im Ansichtsmodell abgerufen. Die Courses-Eigenschaft des Ansichtsmodells wird mit den Course-Entitäten aus der Courses-Navigationseigenschaft des ausgewählten Dozenten geladen.

Die Where-Methode gibt eine Sammlung zurück. In diesem Fall wählt der Filter eine einzelne Entität aus, sodass die Single-Methode aufgerufen wird, um die Sammlung in eine einzelne Instructor-Entität zu konvertieren. Die Instructor-Entität ermöglicht Zugriff auf die Course-Navigationseigenschaft.

Die Single-Methode wird für eine Sammlung verwendet, wenn die Sammlung nur ein Element aufweist. Die Single-Methode löst eine Ausnahme aus, wenn die Sammlung leer ist oder mehr als ein Element vorhanden ist. Eine Alternative ist SingleOrDefault, womit ein Standardwert zurückgegeben wird, wenn die Sammlung leer ist. Für diese Abfrage wird null als Standardwert zurückgegeben.

Wenn ein Kurs ausgewählt ist, füllt der folgende Code die Enrollments-Eigenschaft des Ansichtsmodells:

if (courseID != null)
{
    CourseID = courseID.Value;
    IEnumerable<Enrollment> Enrollments = await _context.Enrollments
        .Where(x => x.CourseID == CourseID)                    
        .Include(i=>i.Student)
        .ToListAsync();                 
    InstructorData.Enrollments = Enrollments;
}

Aktualisieren der Indexseite „Dozenten“

Aktualisieren Sie Pages/Instructors/Index.cshtml mit folgendem Code.

@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel

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

<h2>Instructors</h2>

<p>
    <a asp-page="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.InstructorData.Instructors)
        {
            string selectedRow = "";
            if (item.ID == Model.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.Courses)
                        {
                            @course.CourseID @:  @course.Title <br />
                        }
                    }
                </td>
                <td>
                    <a asp-page="./Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

@if (Model.InstructorData.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.InstructorData.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == Model.CourseID)
            {
                selectedRow = "table-success";
            }
            <tr class="@selectedRow">
                <td>
                    <a asp-page="./Index" asp-route-courseID="@item.CourseID">Select</a>
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr>
        }

    </table>
}

@if (Model.InstructorData.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.InstructorData.Enrollments)
        {
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
}

Durch den vorangehenden Code werden folgende Änderungen vorgenommen:

  • Aktualisiert die page-Anweisung in @page "{id:int?}". "{id:int?}" ist eine Routenvorlage. Die Routenvorlage ändert ganzzahlige Abfragezeichenfolgen in der URL in Routendaten. Klicken Sie beispielsweise auf den Link Auswählen für einen Dozenten, wenn nur die @page-Anweisung eine URL wie die folgende erzeugt:

    https://localhost:5001/Instructors?id=2

    Wenn die Seitenanweisung @page "{id:int?}" ist, ist die URL https://localhost:5001/Instructors/2.

  • Fügt eine Office-Spalte hinzu, die item.OfficeAssignment.Location nur anzeigt, wenn item.OfficeAssignment nicht NULL ist. Da dies eine 1:0..1-Beziehung ist, gibt es möglicherweise keine verwandte OfficeAssignment-Entität.

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Fügt eine Courses-Spalte hinzu, die die Kurse eines jeden Dozenten anzeigt. Weitere Informationen zu dieser Razor-Syntax finden Sie unter Expliziter Zeilenübergang.

  • Fügt Code hinzu, der class="table-success" dynamisch zum tr-Element des ausgewählten Dozenten und Kurses hinzufügt. Hiermit wird mit einer Bootstrapklasse eine Hintergrundfarbe für die ausgewählte Zeile hinzugefügt.

    string selectedRow = "";
    if (item.CourseID == Model.CourseID)
    {
        selectedRow = "table-success";
    }
    <tr class="@selectedRow">
    
  • Fügt einen neuen Link mit der Bezeichnung Select hinzu. Dieser Link sendet die ID des ausgewählten Dozenten an die Index-Methode und legt die Hintergrundfarbe fest.

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    
  • Fügt eine Tabelle mit Kursen für den ausgewählten Dozenten hinzu.

  • Fügt eine Tabelle mit Studentenregistrierungen für den ausgewählten Kurs hinzu.

Führen Sie die Anwendung aus. Klicken Sie auf die Registerkarte Dozenten. Die Seite zeigt den Location (Office) aus der verwandten OfficeAssignment-Entität an. Wenn OfficeAssignment NULL ist, wird eine leere Tabellenzelle angezeigt.

Klicken Sie auf den Link Select für einen Dozenten. Der Zeilenstil ändert sich, und Kurse, die diesem Dozenten zugewiesen sind, werden angezeigt.

Wählen Sie einen Kurs aus, um die Liste der registrierten Studenten und deren Noten einzusehen.

Instructors Index page instructor and course selected

Nächste Schritte

Das nächste Tutorial zeigt, wie verwandte Daten aktualisiert werden.

Dieses Tutorial zeigt, wie verwandte Daten gelesen und angezeigt werden. Verwandte Daten sind Daten, die von EF Core in die Navigationseigenschaften geladen werden.

Die folgenden Abbildungen zeigen die abgeschlossenen Seiten für dieses Tutorial:

Courses Index page

Instructors Index page

Explizites Laden, Eager Loading und Lazy Loading

Es gibt mehrere Möglichkeiten, wie EF Core verwandte Daten in die Navigationseigenschaften einer Entität laden kann:

  • Eager Loading (vorzeitiges Laden). Beim Eager Loading lädt eine Abfrage für einen Entitätstyp auch verwandte Entitäten. Wenn eine Entität gelesen wird, werden ihre zugehörigen Daten abgerufen. Dies führt normalerweise zu einer einzelnen Joinabfrage, die alle Daten abruft, die erforderlich sind. EF Core wird mehrere Abfragen für einige Typen von Eager Loading ausgeben. Das Ausgeben mehrerer Abfragen kann effizienter sein als eine einzelne große Abfrage. Eager Loading wird mit den Include- und ThenInclude-Methoden angegeben.

    Eager loading example

    Eager Loading sendet mehrere Abfragen, wenn eine Sammlungsnavigation enthalten ist:

    • Eine Abfrage für die Hauptabfrage
    • Eine Abfrage für jeden „Sammlungsrand“ in der Ladestruktur.
  • Separate Abfragen mit Load: Die Daten können in separaten Abfragen abgerufen werden und EF Core korrigiert die Navigationseigenschaften. „Korrigieren“ bedeutet, dass EF Core die Navigationseigenschaften automatisch mit Daten füllt. Separate Abfragen mit Load ähneln mehr dem expliziten Laden als dem Eager Loading.

    Separate queries example

    Hinweis:EF Core korrigiert automatisch Navigationseigenschaften für alle anderen Entitäten, die zuvor in die Kontextinstanz geladen wurden. Auch wenn die Daten für eine Navigationseigenschaft nicht explizit eingeschlossen sind, kann die Eigenschaft immer noch aufgefüllt werden, wenn einige oder alle verwandten Entitäten zuvor geladen wurden.

  • Explizites Laden. Wenn die Entität zuerst gelesen wird, werden verwandte Daten nicht abgerufen. Es muss Code geschrieben werden, um die verwandten Daten bei Bedarf abzurufen. Explizites Laden mit separaten Abfragen führt zu mehreren Abfragen, die an die Datenbank gesendet werden. Mit explizitem Laden gibt der Code die zu ladenden Navigationseigenschaften an. Verwenden Sie für explizites Laden die Load-Methode. Beispiel:

    Explicit loading example

  • Lazy Loading (verzögertes Laden). Wenn die Entität zuerst gelesen wird, werden verwandte Daten nicht abgerufen. Wenn zum ersten Mal auf eine Navigationseigenschaft zugegriffen wird, werden die für diese Navigationseigenschaft erforderlichen Daten automatisch abgerufen. Wenn zum ersten Mal auf eine Navigationseigenschaft zugegriffen wird, wird jedes Mal eine Abfrage an die Datenbank gesendet. Lazy Loading kann die Leistung beeinträchtigen, z. B. wenn Entwickler N+1-Muster verwenden, ein übergeordnetes Element laden und über untergeordnete Elemente aufzählen.

Erstellen von Course-Seiten

Die Course-Entität enthält eine Navigationseigenschaft, die die zugehörige Department-Entität enthält.

Course.Department

So zeigen Sie den Namen des zugewiesenen Fachbereichs für einen Kurs an:

  • Laden Sie die zugehörige Department-Entität in die Course.Department-Navigationseigenschaft.
  • Rufen Sie den Namen aus der Name-Eigenschaft der Department-Entität ab.

Gerüstbau von Course-Seiten

  • Befolgen Sie die Anweisungen unter Gerüstbau der Student-Seiten mit den folgenden Ausnahmen:

    • Erstellen Sie einen Ordner Pages/Courses.
    • Verwenden Sie Course als Modellklasse.
    • Verwenden Sie die vorhandene Kontextklasse, anstatt eine neue Klasse zu erstellen.
  • Öffnen Sie Pages/Courses/Index.cshtml.cs, und untersuchen Sie die OnGetAsync-Methode. Die Gerüstbauengine gibt Eager Loading für die Department-Navigationseigenschaft an. Die Include-Methode gibt Eager Loading an.

  • Führen Sie die Anwendung aus, und klicken Sie auf den Link Kurse. Die Abteilungsspalte zeigt die DepartmentID an, die nicht hilfreich ist.

Anzeigen des Fachbereichnamens

Aktualisieren Sie „Pages/Courses/Index.cshtml.cs“ mit dem folgenden Code:

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

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

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

        public IList<Course> Courses { get; set; }

        public async Task OnGetAsync()
        {
            Courses = await _context.Courses
                .Include(c => c.Department)
                .AsNoTracking()
                .ToListAsync();
        }
    }
}

Der vorstehende Code ändert die Course-Eigenschaft in Courses und fügt AsNoTracking hinzu. AsNoTracking verbessert die Leistung, da die zurückgegebenen Entitäten nicht nachverfolgt werden. Die Entitäten müssen nicht nachverfolgt werden, da sie nicht im aktuellen Kontext aktualisiert werden.

Aktualisieren Sie Pages/Courses/Index.cshtml mit folgendem Code.

@page
@model ContosoUniversity.Pages.Courses.IndexModel

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

<h1>Courses</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Courses[0].CourseID)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Courses[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Courses[0].Credits)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Courses[0].Department)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model.Courses)
{
        <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-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Die folgenden Änderungen wurden am Codegerüst vorgenommen:

  • Der Course-Eigenschaftenname wurde in Courses geändert.

  • Die Spalte Anzahl wurde hinzugefügt. Sie zeigt den CourseID-Eigenschaftswert an. Primärschlüssel werden nicht standardmäßig eingerüstet, da sie normalerweise ohne Bedeutung für Endbenutzer sind. Allerdings hat der Primärschlüssel in diesem Fall jedoch Bedeutung.

  • Die Spalte Abteilung wurde geändert, sodass sie jetzt den Namen der Abteilung anzeigt. Der Code zeigt die Name-Eigenschaft der Department-Entität an, die in die Department-Navigationseigenschaft geladen wird:

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

Führen Sie die Anwendung aus. Wählen Sie die Registerkarte Kurse aus, um die Liste mit den Abteilungsnamen anzuzeigen.

Courses Index page

Die OnGetAsync-Methode lädt zugehörige Daten mit der Include-Methode. Die Select-Methode ist eine Alternative, die nur die zugehörigen benötigten Daten lädt. Für die einzelnen Elemente wie Department.Name wird ein INNER JOIN von SQL verwendet. Für Sammlungen wird ein anderer Zugriff auf die Datenbank verwendet, aber Gleiches gilt für den Include-Operator für Sammlungen.

Der folgende Code lädt verwandte Daten mit der Select-Methode:

public IList<CourseViewModel> CourseVM { get; set; }

public async Task OnGetAsync()
{
    CourseVM = await _context.Courses
            .Select(p => new CourseViewModel
            {
                CourseID = p.CourseID,
                Title = p.Title,
                Credits = p.Credits,
                DepartmentName = p.Department.Name
            }).ToListAsync();
}

Der voranstehende Code gibt keine Entitätstypen zurück, weshalb keine Nachverfolgung ausgeführt wird. Weitere Informationen zur EF-Nachverfolgung finden Sie unter Nachverfolgungsabfragen im Vergleich zu Abfragen ohne Nachverfolgung.

Die CourseViewModel:

public class CourseViewModel
{
    public int CourseID { get; set; }
    public string Title { get; set; }
    public int Credits { get; set; }
    public string DepartmentName { get; set; }
}

Ein vollständiges Beispiel finden Sie unter IndexSelect.cshtml und IndexSelect.cshtml.cs.

Erstellen von Instructor-Seiten

In diesem Abschnitt erfolgt der Gerüstbau von Instructor-Seiten, und der Index-Seite des Dozenten werden zugehörige Courses und Enrollments hinzugefügt.

Instructors Index page

Auf dieser Seite werden verwandte Daten auf folgende Weise gelesen und angezeigt:

  • Die Liste der Dozenten zeigt verwandte Daten aus der OfficeAssignment-Entität (Office in der vorherigen Abbildung). Die Instructor- und OfficeAssignment-Entitäten stehen in einer 1:0..1-Beziehung zueinander. Eager Loading wird für die OfficeAssignment-Entitäten verwendet. Eager Loading ist in der Regel effizienter, wenn die verwandten Daten angezeigt werden müssen. In diesem Fall werden die Office-Zuweisungen für die Dozenten angezeigt.
  • Wenn der Benutzer einen Dozenten auswählt, werden zugehörige Course-Entitäten angezeigt. Die Instructor- und Course-Entitäten stehen in einer m:n-Beziehung zueinander. Für die Course-Entitäten und ihre verwandten Department-Entitäten wird das Eager Loading verwendet. In diesem Fall können separate Abfragen effizienter sein, da nur Kurse für den ausgewählten Dozenten benötigt werden. Dieses Beispiel zeigt, wie Eager Loading für Navigationseigenschaften in Entitäten in den Navigationseigenschaften verwendet wird.
  • Wenn der Benutzer einen Kurs auswählt, werden zugehörige Daten aus der Enrollments-Entität angezeigt. In der vorherigen Abbildung sind der Name des Studenten und die Note angezeigt. Die Course- und Enrollment-Entitäten stehen in einer 1:n-Beziehung zueinander.

Erstellen von Anzeigemodellen

Die Dozentenseite zeigt Daten aus drei verschiedenen Tabellen. Ein Ansichtsmodell ist erforderlich, das drei Eigenschaften enthält, die die drei Tabellen darstellen.

Erstellen Sie SchoolViewModels/InstructorIndexData.cs mit dem folgenden Code:

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

Gerüstbau von Instructor-Seiten

  • Befolgen Sie die Anweisungen unter Gerüstbau der Student-Seiten mit den folgenden Ausnahmen:

    • Erstellen Sie einen Ordner Pages/Instructors.
    • Verwenden Sie Instructor als Modellklasse.
    • Verwenden Sie die vorhandene Kontextklasse, anstatt eine neue Klasse zu erstellen.

Führen Sie die App aus, und navigieren Sie zur Seite Instructors, um zu sehen, wie die Gerüstbauseite aussieht, bevor Sie sie aktualisieren.

Aktualisieren Sie Pages/Instructors/Index.cshtml.cs mit folgendem Code:

using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;  // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

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

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

        public InstructorIndexData InstructorData { get; set; }
        public int InstructorID { get; set; }
        public int CourseID { get; set; }

        public async Task OnGetAsync(int? id, int? courseID)
        {
            InstructorData = new InstructorIndexData();
            InstructorData.Instructors = await _context.Instructors
                .Include(i => i.OfficeAssignment)                 
                .Include(i => i.CourseAssignments)
                    .ThenInclude(i => i.Course)
                        .ThenInclude(i => i.Department)
                .Include(i => i.CourseAssignments)
                    .ThenInclude(i => i.Course)
                        .ThenInclude(i => i.Enrollments)
                            .ThenInclude(i => i.Student)
                .AsNoTracking()
                .OrderBy(i => i.LastName)
                .ToListAsync();

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

            if (courseID != null)
            {
                CourseID = courseID.Value;
                var selectedCourse = InstructorData.Courses
                    .Where(x => x.CourseID == courseID).Single();
                InstructorData.Enrollments = selectedCourse.Enrollments;
            }
        }
    }
}

Die OnGetAsync-Methode akzeptiert optional Routendaten für die ID des ausgewählten Dozenten.

Untersuchen Sie die Abfrage in der Datei Pages/Instructors/Index.cshtml.cs:

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

Der Code gibt Eager Loading für die folgenden Navigationseigenschaften an:

  • Instructor.OfficeAssignment
  • Instructor.CourseAssignments
    • CourseAssignments.Course
      • Course.Department
      • Course.Enrollments
        • Enrollment.Student

Beachten Sie die Wiederholung der Include- und ThenInclude-Methoden für CourseAssignments und Course. Diese Wiederholung ist erforderlich, um Eager Loading für zwei Navigationseigenschaften der Course-Entität anzugeben.

Der folgende Code wird ausgeführt, wenn ein Dozent ausgewählt wird (id != null).

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

Der ausgewählte Dozent wird aus der Liste der Dozenten im Ansichtsmodell abgerufen. Die Courses-Eigenschaft des Ansichtsmodells wird mit den Course-Entitäten aus der CourseAssignments-Navigationseigenschaft dieses Dozenten geladen.

Die Where-Methode gibt eine Sammlung zurück. In diesem Fall wählt der Filter jedoch eine einzelne Entität aus, sodass die Single-Methode aufgerufen wird, um die Sammlung in eine einzelne Instructor-Entität zu konvertieren. Die Instructor-Entität ermöglicht Zugriff auf die CourseAssignments-Eigenschaft. CourseAssignments ermöglicht Zugriff auf die verwandten Course-Entitäten.

Instructor-to-Courses m:M

Die Single-Methode wird für eine Sammlung verwendet, wenn die Sammlung nur ein Element aufweist. Die Single-Methode löst eine Ausnahme aus, wenn die Sammlung leer ist oder mehr als ein Element vorhanden ist. Eine Alternative ist SingleOrDefault, womit ein Standardwert (in diesem Fall 0 (null)) zurückgegeben wird, wenn die Sammlung leer ist.

Wenn ein Kurs ausgewählt ist, füllt der folgende Code die Enrollments-Eigenschaft des Ansichtsmodells:

if (courseID != null)
{
    CourseID = courseID.Value;
    var selectedCourse = InstructorData.Courses
        .Where(x => x.CourseID == courseID).Single();
    InstructorData.Enrollments = selectedCourse.Enrollments;
}

Aktualisieren der Indexseite „Dozenten“

Aktualisieren Sie Pages/Instructors/Index.cshtml mit folgendem Code.

@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel

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

<h2>Instructors</h2>

<p>
    <a asp-page="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.InstructorData.Instructors)
        {
            string selectedRow = "";
            if (item.ID == Model.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-page="./Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

@if (Model.InstructorData.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.InstructorData.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == Model.CourseID)
            {
                selectedRow = "table-success";
            }
            <tr class="@selectedRow">
                <td>
                    <a asp-page="./Index" asp-route-courseID="@item.CourseID">Select</a>
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr>
        }

    </table>
}

@if (Model.InstructorData.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.InstructorData.Enrollments)
        {
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
}

Durch den vorangehenden Code werden folgende Änderungen vorgenommen:

  • Aktualisiert die page-Anweisung von @page auf @page "{id:int?}". "{id:int?}" ist eine Routenvorlage. Die Routenvorlage ändert ganzzahlige Abfragezeichenfolgen in der URL in Routendaten. Klicken Sie beispielsweise auf den Link Auswählen für einen Dozenten, wenn nur die @page-Anweisung eine URL wie die folgende erzeugt:

    https://localhost:5001/Instructors?id=2

    Wenn die Seitenanweisung @page "{id:int?}" ist, lautet die URL folgendermaßen:

    https://localhost:5001/Instructors/2

  • Fügt eine Office-Spalte hinzu, die item.OfficeAssignment.Location nur anzeigt, wenn item.OfficeAssignment nicht NULL ist. Da dies eine 1:0..1-Beziehung ist, gibt es möglicherweise keine verwandte OfficeAssignment-Entität.

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Fügt eine Courses-Spalte hinzu, die die Kurse eines jeden Dozenten anzeigt. Weitere Informationen zu dieser Razor-Syntax finden Sie unter Expliziter Zeilenübergang.

  • Fügt Code hinzu, der class="table-success" dynamisch zum tr-Element des ausgewählten Dozenten und Kurses hinzufügt. Hiermit wird mit einer Bootstrapklasse eine Hintergrundfarbe für die ausgewählte Zeile hinzugefügt.

    string selectedRow = "";
    if (item.CourseID == Model.CourseID)
    {
        selectedRow = "table-success";
    }
    <tr class="@selectedRow">
    
  • Fügt einen neuen Link mit der Bezeichnung Select hinzu. Dieser Link sendet die ID des ausgewählten Dozenten an die Index-Methode und legt die Hintergrundfarbe fest.

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    
  • Fügt eine Tabelle mit Kursen für den ausgewählten Dozenten hinzu.

  • Fügt eine Tabelle mit Studentenregistrierungen für den ausgewählten Kurs hinzu.

Führen Sie die Anwendung aus. Klicken Sie auf die Registerkarte Dozenten. Die Seite zeigt den Location (Office) aus der verwandten OfficeAssignment-Entität an. Wenn OfficeAssignment NULL ist, wird eine leere Tabellenzelle angezeigt.

Klicken Sie auf den Link Select für einen Dozenten. Der Zeilenstil ändert sich, und Kurse, die diesem Dozenten zugewiesen sind, werden angezeigt.

Wählen Sie einen Kurs aus, um die Liste der registrierten Studenten und deren Noten einzusehen.

Instructors Index page instructor and course selected

Verwenden von „Single“

Die Single-Methode kann die Where-Bedingung übergehen, anstatt die Where-Methode separat aufzurufen:

public async Task OnGetAsync(int? id, int? courseID)
{
    InstructorData = new InstructorIndexData();

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

    if (id != null)
    {
        InstructorID = id.Value;
        Instructor instructor = InstructorData.Instructors.Single(
            i => i.ID == id.Value);
        InstructorData.Courses = instructor.CourseAssignments.Select(
            s => s.Course);
    }

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

Die Verwendung von Single mit einer Where-Bedingung ist eine Frage der persönlichen Präferenz. Sie bietet keine Vorteile gegenüber der Verwendung der Where-Methode.

Explizites Laden

Der aktuelle Code gibt Eager Loading für Enrollments und Students an:

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

Angenommen, Benutzer möchten Registrierungen für einen Kurs nur selten anzeigen lassen. In diesem Fall wäre es eine Optimierung, die Registrierungsdaten nur dann zu laden, wenn diese angefordert werden. In diesem Abschnitt wird die OnGetAsync aktualisiert, um das explizite Laden von Enrollments und Students zu verwenden.

Aktualisieren Sie Pages/Instructors/Index.cshtml.cs mit folgendem Code.

using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;  // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

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

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

        public InstructorIndexData InstructorData { get; set; }
        public int InstructorID { get; set; }
        public int CourseID { get; set; }

        public async Task OnGetAsync(int? id, int? courseID)
        {
            InstructorData = new InstructorIndexData();
            InstructorData.Instructors = await _context.Instructors
                .Include(i => i.OfficeAssignment)                 
                .Include(i => i.CourseAssignments)
                    .ThenInclude(i => i.Course)
                        .ThenInclude(i => i.Department)
                //.Include(i => i.CourseAssignments)
                //    .ThenInclude(i => i.Course)
                //        .ThenInclude(i => i.Enrollments)
                //            .ThenInclude(i => i.Student)
                //.AsNoTracking()
                .OrderBy(i => i.LastName)
                .ToListAsync();

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

            if (courseID != null)
            {
                CourseID = courseID.Value;
                var selectedCourse = InstructorData.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();
                }
                InstructorData.Enrollments = selectedCourse.Enrollments;
            }
        }
    }
}

Der vorangehende Code löscht die ThenInclude-Methodenaufrufe für Registrierung und Studentendaten. Wenn ein Kurs ausgewählt ist, ruft der explizit geladene Code Folgendes ab:

  • Die Enrollment-Entitäten für den ausgewählten Kurs.
  • Die Student-Entitäten für jede Enrollment.

Beachten Sie, dass der vorherige Code .AsNoTracking() auskommentiert. Navigationseigenschaften können nur für nachverfolgte Entitäten explizit geladen werden.

Testen Sie die App. Aus Benutzersicht verhält sich die Anwendung identisch mit der vorherigen Version.

Nächste Schritte

Das nächste Tutorial zeigt, wie verwandte Daten aktualisiert werden.

In diesem Tutorial werden verwandte Daten gelesen und angezeigt. Verwandte Daten sind Daten, die von EF Core in die Navigationseigenschaften geladen werden.

Wenn nicht zu lösende Probleme auftreten, laden Sie die fertige App herunter, oder zeigen Sie diese an.Herunterladen der Anweisungen

Die folgenden Abbildungen zeigen die abgeschlossenen Seiten für dieses Tutorial:

Courses Index page

Instructors Index page

Es gibt mehrere Möglichkeiten, wie EF Core verwandte Daten in die Navigationseigenschaften einer Entität laden kann:

  • Eager Loading (vorzeitiges Laden). Beim Eager Loading lädt eine Abfrage für einen Entitätstyp auch verwandte Entitäten. Wenn die Entität gelesen wird, werden ihre verwandten Daten abgerufen. Dies führt normalerweise zu einer einzelnen Joinabfrage, die alle Daten abruft, die erforderlich sind. EF Core wird mehrere Abfragen für einige Typen von Eager Loading ausgeben. Die Ausgabe mehrerer Abfragen kann effizienter sein, als dies bei einigen Abfragen in EF6 der Fall war. Dort war nur eine einzelne Abfrage vorhanden. Eager Loading wird mit den Include- und ThenInclude-Methoden angegeben.

    Eager loading example

    Eager Loading sendet mehrere Abfragen, wenn eine Sammlungsnavigation enthalten ist:

    • Eine Abfrage für die Hauptabfrage
    • Eine Abfrage für jeden „Sammlungsrand“ in der Ladestruktur.
  • Separate Abfragen mit Load: Die Daten können in separaten Abfragen abgerufen werden und EF Core korrigiert die Navigationseigenschaften. „Korrigieren“ bedeutet, dass EF Core die Navigationseigenschaften automatisch mit Daten füllt. Separate Abfragen mit Load ähneln mehr dem expliziten Laden als dem Eager Loading.

    Separate queries example

    Hinweis: EF Core korrigiert automatisch Navigationseigenschaften für alle anderen Entitäten, die zuvor in die Kontextinstanz geladen wurden. Auch wenn die Daten für eine Navigationseigenschaft nicht explizit eingeschlossen sind, kann die Eigenschaft immer noch aufgefüllt werden, wenn einige oder alle verwandten Entitäten zuvor geladen wurden.

  • Explizites Laden. Wenn die Entität zuerst gelesen wird, werden verwandte Daten nicht abgerufen. Es muss Code geschrieben werden, um die verwandten Daten bei Bedarf abzurufen. Explizites Laden mit separaten Abfragen führt zu mehreren Abfragen, die an die Datenbank gesendet werden. Mit explizitem Laden gibt der Code die zu ladenden Navigationseigenschaften an. Verwenden Sie für explizites Laden die Load-Methode. Beispiel:

    Explicit loading example

  • Lazy Loading (verzögertes Laden). Lazy Loading wurde in Version 2.1 zu EF Core hinzugefügt. Wenn die Entität zuerst gelesen wird, werden verwandte Daten nicht abgerufen. Wenn zum ersten Mal auf eine Navigationseigenschaft zugegriffen wird, werden die für diese Navigationseigenschaft erforderlichen Daten automatisch abgerufen. Wenn zum ersten Mal auf eine Navigationseigenschaft zugegriffen wird, wird jedes Mal eine Abfrage an die Datenbank geschickt.

  • Der Select-Operator lädt nur die erforderlichen verwandten Daten.

Erstellen einer Kursseite, die den Abteilungsnamen anzeigt

Die Course-Entität enthält eine Navigationseigenschaft, welche die Department-Entität enthält. Die Department-Entität enthält die Abteilung, der der Kurs zugewiesen ist.

So zeigen Sie den Namen der zugewiesenen Abteilung in einer Kursliste an:

  • Rufen Sie Name-Eigenschaft aus der Department-Entität ab.
  • Die Department-Entität stammt aus der Course.Department-Navigationseigenschaft.

Course.Department

Erstellen des Gerüsts für das Kursmodell

Führen Sie die Schritte unter Erstellen des Gerüsts für das Studentenmodell aus, und verwenden Sie Course für die Modellklasse.

Der vorherige Befehl erstellt ein Gerüst für das Course-Modell. Öffnen Sie das Projekt in Visual Studio.

Öffnen Sie Pages/Courses/Index.cshtml.cs, und untersuchen Sie die OnGetAsync-Methode. Die Gerüstbauengine gibt Eager Loading für die Department-Navigationseigenschaft an. Die Include-Methode gibt Eager Loading an.

Führen Sie die Anwendung aus, und klicken Sie auf den Link Kurse. Die Abteilungsspalte zeigt die DepartmentID an, die nicht hilfreich ist.

Aktualisieren Sie die OnGetAsync-Methode mit folgendem Code:

public async Task OnGetAsync()
{
    Course = await _context.Courses
        .Include(c => c.Department)
        .AsNoTracking()
        .ToListAsync();
}

Der vorangehende Code fügt AsNoTracking hinzu. AsNoTracking verbessert die Leistung, da die zurückgegebenen Entitäten nicht nachverfolgt werden. Die Entitäten werden nicht nachverfolgt, da sie nicht im aktuellen Kontext aktualisiert werden.

Aktualisieren Sie Pages/Courses/Index.cshtml mit dem folgenden hervorgehobenen Markup:

@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
    ViewData["Title"] = "Courses";
}

<h2>Courses</h2>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Course[0].CourseID)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Course[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Course[0].Credits)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Course[0].Department)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Course)
        {
            <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-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Die folgenden Änderungen wurden am Codegerüst vorgenommen:

  • Die Überschrift wurde von „Index“ in „Kurse“ geändert.

  • Die Spalte Anzahl wurde hinzugefügt. Sie zeigt den CourseID-Eigenschaftswert an. Primärschlüssel werden nicht standardmäßig eingerüstet, da sie normalerweise ohne Bedeutung für Endbenutzer sind. Allerdings hat der Primärschlüssel in diesem Fall jedoch Bedeutung.

  • Die Spalte Abteilung wurde geändert, sodass sie jetzt den Namen der Abteilung anzeigt. Der Code zeigt die Name-Eigenschaft der Department-Entität an, die in die Department-Navigationseigenschaft geladen wird:

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

Führen Sie die Anwendung aus. Wählen Sie die Registerkarte Kurse aus, um die Liste mit den Abteilungsnamen anzuzeigen.

Courses Index page

Die OnGetAsync-Methode lädt verwandte Daten mit der Include-Methode:

public async Task OnGetAsync()
{
    Course = await _context.Courses
        .Include(c => c.Department)
        .AsNoTracking()
        .ToListAsync();
}

Der Select-Operator lädt nur die erforderlichen verwandten Daten. Für die einzelnen Elemente wie Department.Name wird ein INNER JOIN von SQL verwendet. Für Sammlungen wird ein anderer Zugriff auf die Datenbank verwendet, aber Gleiches gilt für den Include-Operator für Sammlungen.

Der folgende Code lädt verwandte Daten mit der Select-Methode:

public IList<CourseViewModel> CourseVM { get; set; }

public async Task OnGetAsync()
{
    CourseVM = await _context.Courses
            .Select(p => new CourseViewModel
            {
                CourseID = p.CourseID,
                Title = p.Title,
                Credits = p.Credits,
                DepartmentName = p.Department.Name
            }).ToListAsync();
}

Die CourseViewModel:

public class CourseViewModel
{
    public int CourseID { get; set; }
    public string Title { get; set; }
    public int Credits { get; set; }
    public string DepartmentName { get; set; }
}

Ein vollständiges Beispiel finden Sie unter IndexSelect.cshtml und IndexSelect.cshtml.cs.

Erstellen einer Dozentenseite, die Kurse und Registrierungen anzeigt

In diesem Abschnitt wird die Dozentenseite erstellt.

Instructors Index page

Auf dieser Seite werden verwandte Daten auf folgende Weise gelesen und angezeigt:

  • Die Liste der Dozenten zeigt verwandte Daten aus der OfficeAssignment-Entität (Office in der vorherigen Abbildung). Die Instructor- und OfficeAssignment-Entitäten stehen in einer 1:0..1-Beziehung zueinander. Eager Loading wird für die OfficeAssignment-Entitäten verwendet. Eager Loading ist in der Regel effizienter, wenn die verwandten Daten angezeigt werden müssen. In diesem Fall werden die Office-Zuweisungen für die Dozenten angezeigt.
  • Wenn der Benutzer einen Dozenten auswählt (Harui in der vorherigen Abbildung), werden verwandte Course-Entitäten angezeigt. Die Instructor- und Course-Entitäten stehen in einer m:n-Beziehung zueinander. Für die Course-Entitäten und ihre verwandten Department-Entitäten wird das Eager Loading verwendet. In diesem Fall können separate Abfragen effizienter sein, da nur Kurse für den ausgewählten Dozenten benötigt werden. Dieses Beispiel zeigt, wie Eager Loading für Navigationseigenschaften in Entitäten in den Navigationseigenschaften verwendet wird.
  • Wenn der Benutzer einen Kurs auswählt (Chemie in der vorherigen Abbildung), werden verwandte Daten aus der Enrollments-Entität angezeigt. In der vorherigen Abbildung sind der Name des Studenten und die Note angezeigt. Die Course- und Enrollment-Entitäten stehen in einer 1:n-Beziehung zueinander.

Erstellen eines Ansichtsmodells für die Indexansicht „Dozenten“

Die Dozentenseite zeigt Daten aus drei verschiedenen Tabellen. Es wird ein Ansichtsmodell erstellt, das die drei Entitäten enthält, die die drei Tabellen darstellen.

Erstellen Sie im Ordner SchoolViewModels die Datei InstructorIndexData.cs mit dem folgenden Code:

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

Gerüstbau für das Dozentenmodell

Führen Sie die Schritte unter Erstellen des Gerüsts für das Studentenmodell aus, und verwenden Sie Instructor für die Modellklasse.

Der vorherige Befehl erstellt ein Gerüst für das Instructor-Modell. Führen Sie die Anwendung aus, und navigieren Sie zur Dozentenseite.

Ersetzen Sie den Code Pages/Instructors/Index.cshtml.cs durch folgenden Code:

using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;  // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

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

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

        public InstructorIndexData Instructor { get; set; }
        public int InstructorID { get; set; }

        public async Task OnGetAsync(int? id)
        {
            Instructor = new InstructorIndexData();
            Instructor.Instructors = await _context.Instructors
                  .Include(i => i.OfficeAssignment)
                  .Include(i => i.CourseAssignments)
                    .ThenInclude(i => i.Course)
                  .AsNoTracking()
                  .OrderBy(i => i.LastName)
                  .ToListAsync();

            if (id != null)
            {
                InstructorID = id.Value;
            }           
        }
    }
}

Die OnGetAsync-Methode akzeptiert optional Routendaten für die ID des ausgewählten Dozenten.

Untersuchen Sie die Abfrage in der Datei Pages/Instructors/Index.cshtml.cs:

Instructor.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

Die Abfrage enthält zwei Dinge:

  • OfficeAssignment: In der Dozentenansicht angezeigt.
  • CourseAssignments: Welche Kurse gegeben werden.

Aktualisieren der Indexseite „Dozenten“

Aktualisieren Sie Pages/Instructors/Index.cshtml mit dem folgenden Markup:

@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel

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

<h2>Instructors</h2>

<p>
    <a asp-page="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.Instructor.Instructors)
        {
            string selectedRow = "";
            if (item.ID == Model.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-page="./Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Das oben stehende Markup führt die folgenden Änderungen durch:

  • Aktualisiert die page-Anweisung von @page auf @page "{id:int?}". "{id:int?}" ist eine Routenvorlage. Die Routenvorlage ändert ganzzahlige Abfragezeichenfolgen in der URL in Routendaten. Klicken Sie beispielsweise auf den Link Auswählen für einen Dozenten, wenn nur die @page-Anweisung eine URL wie die folgende erzeugt:

    http://localhost:1234/Instructors?id=2

    Wenn die Seitenanweisung @page "{id:int?}" ist, sieht die vorherige URL wie folgt aus:

    http://localhost:1234/Instructors/2

  • Der Seitentitel lautet Dozenten.

  • Es wurde eine Office-Spalte hinzugefügt, die item.OfficeAssignment.Location nur anzeigt, wenn item.OfficeAssignment nicht gleich 0 (null) ist. Da dies eine 1:0..1-Beziehung ist, gibt es möglicherweise keine verwandte OfficeAssignment-Entität.

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Es wurde eine Kurse-Spalte hinzugefügt, die die Kurse eines jeden Dozenten anzeigt. Weitere Informationen zu dieser Razor-Syntax finden Sie unter Expliziter Zeilenübergang.

  • Es wurde Code hinzugefügt, der class="success" dynamisch zum tr-Element des ausgewählten Dozenten hinzufügt. Hiermit wird eine Hintergrundfarbe mit einer Bootstrapklasse für die ausgewählte Zeile hinzugefügt.

    string selectedRow = "";
    if (item.CourseID == Model.CourseID)
    {
        selectedRow = "success";
    }
    <tr class="@selectedRow">
    
  • Es wurde ein neuer Link mit der Bezeichnung Auswählen hinzugefügt. Dieser Link sendet die ID des ausgewählten Dozenten an die Index-Methode und legt die Hintergrundfarbe fest.

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

Führen Sie die Anwendung aus. Klicken Sie auf die Registerkarte Dozenten. Die Seite zeigt den Location (Office) aus der verwandten OfficeAssignment-Entität an. Wenn OfficeAssignment gleich ist 0 (null), wird eine leere Tabellenzelle angezeigt.

Klicken Sie auf den Link Auswählen. Der Zeilenstil verändert sich.

Hinzufügen von Kursen eines ausgewählten Dozenten

Aktualisieren Sie die OnGetAsync-Methode in Pages/Instructors/Index.cshtml.cs mit folgendem Code:

public async Task OnGetAsync(int? id, int? courseID)
{
    Instructor = new InstructorIndexData();
    Instructor.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
          .AsNoTracking()
          .OrderBy(i => i.LastName)
          .ToListAsync();

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

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

Fügen Sie public int CourseID { get; set; } hinzu.

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

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

    public InstructorIndexData Instructor { get; set; }
    public int InstructorID { get; set; }
    public int CourseID { get; set; }

    public async Task OnGetAsync(int? id, int? courseID)
    {
        Instructor = new InstructorIndexData();
        Instructor.Instructors = await _context.Instructors
              .Include(i => i.OfficeAssignment)
              .Include(i => i.CourseAssignments)
                .ThenInclude(i => i.Course)
                    .ThenInclude(i => i.Department)
              .AsNoTracking()
              .OrderBy(i => i.LastName)
              .ToListAsync();

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

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

Überprüfen Sie die aktualisierte Abfrage:

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

Die vorhergehende Abfrage fügt die Department-Entitäten hinzu.

Der folgende Code wird ausgeführt, wenn ein Dozent ausgewählt wird (id != null). Der ausgewählte Dozent wird aus der Liste der Dozenten im Ansichtsmodell abgerufen. Die Courses-Eigenschaft des Ansichtsmodells wird mit den Course-Entitäten aus der CourseAssignments-Navigationseigenschaft dieses Dozenten geladen.

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

Die Where-Methode gibt eine Sammlung zurück. In der vorherigen Where-Methode wird nur eine einzige Instructor-Entität zurückgegeben. Die Single-Methode konvertiert die Sammlung in eine einzelne Instructor-Entität. Die Instructor-Entität ermöglicht Zugriff auf die CourseAssignments-Eigenschaft. CourseAssignments ermöglicht Zugriff auf die verwandten Course-Entitäten.

Instructor-to-Courses m:M

Die Single-Methode wird für eine Sammlung verwendet, wenn die Sammlung nur ein Element aufweist. Die Single-Methode löst eine Ausnahme aus, wenn die Sammlung leer ist oder mehr als ein Element vorhanden ist. Eine Alternative ist SingleOrDefault, womit ein Standardwert (in diesem Fall 0 (null)) zurückgegeben wird, wenn die Sammlung leer ist. Für eine leere Sammlung wird SingleOrDefault verwendet:

  • Löst eine Ausnahme aus (auf der Suche nach einer Courses-Eigenschaft eines Nullverweises).
  • Die Ausnahmemeldung würde die Ursache des Problems weniger deutlich angeben.

Wenn ein Kurs ausgewählt ist, füllt der folgende Code die Enrollments-Eigenschaft des Ansichtsmodells:

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

Fügen Sie das folgende Markup am Ende der Razor-Seite Pages/Instructors/Index.cshtml hinzu:

                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

@if (Model.Instructor.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.Instructor.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == Model.CourseID)
            {
                selectedRow = "success";
            }
            <tr class="@selectedRow">
                <td>
                    <a asp-page="./Index" asp-route-courseID="@item.CourseID">Select</a>
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr>
        }

    </table>
}

Das vorhergehende Markup zeigt eine Liste der Kurse eines bestimmten Dozenten an, wenn einer ausgewählt wird.

Testen Sie die App. Klicken Sie auf den Link Auswählen auf der Dozentenseite.

Anzeigen der Studentendaten

In diesem Abschnitt wird die Anwendung aktualisiert, um die Studentendaten für einen ausgewählten Kurs anzuzeigen.

Aktualisieren Sie die Abfrage in der OnGetAsync-Methode in Pages/Instructors/Index.cshtml.cs mit dem folgenden Code:

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

Aktualisieren Sie Pages/Instructors/Index.cshtml. Fügen Sie dem Dateiende das folgende Markup hinzu:


@if (Model.Instructor.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.Instructor.Enrollments)
        {
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
}

Das vorhergehende Markup zeigt eine Liste der Studenten, die im ausgewählten Kurs registriert sind.

Aktualisieren Sie die Seite. Wählen Sie einen Dozenten aus. Wählen Sie einen Kurs aus, um die Liste der registrierten Studenten und deren Noten einzusehen.

Instructors Index page instructor and course selected

Verwenden von „Single“

Die Single-Methode kann die Where-Bedingung übergehen, anstatt die Where-Methode separat aufzurufen:

public async Task OnGetAsync(int? id, int? courseID)
{
    Instructor = new InstructorIndexData();

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

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

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

Der vorangehende Single-Ansatz bietet keine Vorteile gegenüber Where. Einige Entwickler bevorzugen einfach den Single-Ansatz.

Explizites Laden

Der aktuelle Code gibt Eager Loading für Enrollments und Students an:

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

Angenommen, Benutzer möchten Registrierungen für einen Kurs nur selten anzeigen lassen. In diesem Fall wäre es eine Optimierung, die Registrierungsdaten nur dann zu laden, wenn diese angefordert werden. In diesem Abschnitt wird die OnGetAsync aktualisiert, um das explizite Laden von Enrollments und Students zu verwenden.

Aktualisieren Sie OnGetAsync mit dem folgenden Code:

public async Task OnGetAsync(int? id, int? courseID)
{
    Instructor = new InstructorIndexData();
    Instructor.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)                 
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
            //.Include(i => i.CourseAssignments)
            //    .ThenInclude(i => i.Course)
            //        .ThenInclude(i => i.Enrollments)
            //            .ThenInclude(i => i.Student)
         // .AsNoTracking()
          .OrderBy(i => i.LastName)
          .ToListAsync();


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

    if (courseID != null)
    {
        CourseID = courseID.Value;
        var selectedCourse = Instructor.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();
        }
        Instructor.Enrollments = selectedCourse.Enrollments;
    }
}

Der vorangehende Code löscht die ThenInclude-Methodenaufrufe für Registrierung und Studentendaten. Wenn ein Kurs ausgewählt ist, ruft der hervorgehobene Code Folgendes ab:

  • Die Enrollment-Entitäten für den ausgewählten Kurs.
  • Die Student-Entitäten für jede Enrollment.

Beachten Sie, dass der vorherige Code .AsNoTracking() auskommentiert. Navigationseigenschaften können nur für nachverfolgte Entitäten explizit geladen werden.

Testen Sie die App. Aus Benutzersicht verhält sich die Anwendung identisch mit der vorherigen Version.

Das nächste Tutorial zeigt, wie verwandte Daten aktualisiert werden.

Zusätzliche Ressourcen