Sdílet prostřednictvím


Část 6, Razor stránky s EF Core ASP.NET jádrem – čtení souvisejících dat

Tom Dykstra, Jon P Smith a Rick Anderson

Webová aplikace Contoso University ukazuje, jak vytvářet Razor webové aplikace Pages pomocí EF Core sady Visual Studio. Informace o sérii kurzů najdete v prvním kurzu.

Pokud narazíte na problémy, které nemůžete vyřešit, stáhněte si dokončenou aplikaci a porovnejte tento kód s tím, co jste vytvořili podle kurzu.

V tomto kurzu se dozvíte, jak číst a zobrazovat související data. Související data jsou data, která se EF Core načítají do navigačních vlastností.

Následující ilustrace ukazují dokončené stránky pro tento kurz:

Stránka rejstříku kurzů

Indexová stránka instruktorů

Dychtivé, explicitní a opožděné načítání

Existuje několik způsobů, jak EF Core načíst související data do navigačních vlastností entity:

  • Dychtivá načítání. Načítání dychtivosti spočívá v tom, že dotaz na jeden typ entity také načte související entity. Při čtení entity se načtou související data. Výsledkem je obvykle jeden dotaz spojení, který načte všechna potřebná data. EF Core vydá několik dotazů pro některé typy dychtivého načítání. Vydávání více dotazů může být efektivnější než velký jeden dotaz. Načítání s dychtivou zátěží je určeno metodami Include a ThenInclude metodami.

    Příklad načítání s dychtivou zátěží

    Načítání dychtivého načítání odesílá více dotazů, když je zahrnuta navigace v kolekci:

    • Jeden dotaz pro hlavní dotaz
    • Jeden dotaz pro každou "hranu" kolekce ve stromu zatížení.
  • Samostatné dotazy s Load: Data lze načíst v samostatných dotazech a EF Core "opraví" navigační vlastnosti. "Opravy" znamená, že EF Core automaticky naplní navigační vlastnosti. Samostatné dotazy s více než dychtivým načítáním Load jsou spíše explicitní než dychtivé načítání.

    Příklad samostatných dotazů

    Poznámka:EF Core Automaticky opravuje navigační vlastnosti na všechny ostatní entity, které byly dříve načteny do kontextové instance. I když data pro navigační vlastnost nejsou explicitně zahrnuta, může být vlastnost stále naplněna, pokud některé nebo všechny související entity byly dříve načteny.

  • Explicitní načítání. Při prvním čtení entity se související data nenačtou. Kód musí být zapsán pro načtení souvisejících dat v případě potřeby. Explicitní načítání pomocí samostatných dotazů vede k několika dotazům odesílaným do databáze. Při explicitním načtení kód určuje vlastnosti navigace, které se mají načíst. Použijte metodu Load k explicitnímu načítání. Příklad:

    Příklad explicitního načítání

  • Opožděné načítání. Při prvním čtení entity se související data nenačtou. Při prvním přístupu k navigační vlastnosti se automaticky načtou data potřebná pro tuto navigační vlastnost. Dotaz se odešle do databáze při prvním přístupu k navigační vlastnosti. Opožděné načítání může poškodit výkon, například když vývojáři používají dotazy N+1. N+1 dotazy načtou nadřazený objekt a vytvoří výčet prostřednictvím podřízených položek.

Vytvoření stránek kurzu

Entita Course obsahuje navigační vlastnost, která obsahuje související Department entitu.

Course.Department

Zobrazení názvu přiřazeného oddělení pro kurz:

  • Načtěte související Department entitu do Course.Department navigační vlastnosti.
  • Získá název z Department vlastnosti entity Name .

Stránky kurzu pro generování uživatelského rozhraní

  • Postupujte podle pokynů na stránkách studentů uživatelského rozhraní s následujícími výjimkami:

    • Vytvořte složku Pages/Courses.
    • Používá Course se pro třídu modelu.
    • Místo vytvoření nové třídy použijte existující třídu kontextu.
  • Otevřete Pages/Courses/Index.cshtml.cs a prozkoumejte metodu OnGetAsync . Modul generování uživatelského rozhraní zadal dychtivého načítání pro Department navigační vlastnost. Metoda Include určuje dychtivé načítání.

  • Spusťte aplikaci a vyberte odkaz Kurzy . Ve sloupci oddělení se DepartmentIDzobrazí hodnota , která není užitečná.

Zobrazení názvu oddělení

Aktualizujte stránky/ kurzy/Index.cshtml.cs následujícím kódem:

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

Předchozí kód změní Course vlastnost na Courses a přidá AsNoTracking.

Dotazy bez sledování jsou užitečné, když se výsledky používají ve scénáři jen pro čtení. Obvykle jsou rychlejší, protože není potřeba nastavovat informace o sledování změn. Pokud entity načtené z databáze nemusí být aktualizovány, pak dotaz bez sledování bude pravděpodobně fungovat lépe než sledovací dotaz.

V některých případech je sledovací dotaz efektivnější než dotaz bez sledování. Další informace naleznete v tématu Sledování vs. Dotazy bez sledování. V předchozím kódu se volá, AsNoTracking protože entity se v aktuálním kontextu neaktualizují.

Aktualizujte Pages/Courses/Index.cshtml následujícím kódem.

@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>

V kódu vygenerovaného uživatelského rozhraní byly provedeny následující změny:

  • Course Změna názvu vlastnosti na Courses.

  • Přidali jsme sloupec Číslo , který zobrazuje CourseID hodnotu vlastnosti. Ve výchozím nastavení se primární klíče nevygenerují, protože obvykle nejsou pro koncové uživatele smysluplné. V tomto případě je ale primární klíč smysluplný.

  • Změnili jsme sloupec Oddělení tak, aby zobrazoval název oddělení. Kód zobrazí Name vlastnost Department entity, která je načtena do Department navigační vlastnosti:

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

Spusťte aplikaci a výběrem karty Kurzy zobrazte seznam s názvy oddělení.

Stránka rejstříku kurzů

Metoda OnGetAsync načte související data s metodou Include . Metoda Select je alternativou, která načte pouze související data potřebná. Pro jednotlivé položky, jako je tomu v případě, že Department.Name používá .SQL INNER JOIN U kolekcí používá jiný přístup k databázi, ale operátor Include v kolekcích.

Následující kód načte související data s metodou Select :

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

Předchozí kód nevrací žádné typy entit, proto se neprovádí žádné sledování. Další informace o sledování EF naleznete v tématu Sledování vs. Dotazy bez sledování.

Pomocná rutina CourseViewModel:

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

Kompletní stránky najdete v Razor tématu IndexSelectModel.

Vytvoření stránek instruktora

Tato část vygeneruje stránky instruktora a přidá související kurzy a registrace na stránku Index instruktorů.

Indexová stránka instruktorů

Tato stránka čte a zobrazuje související data následujícími způsoby:

  • Seznam instruktorů zobrazuje související data z OfficeAssignment entity (Office na předchozím obrázku). OfficeAssignment Entity Instructor jsou v relaci 1:0 nebo 1. Pro entity se používá dychtivé OfficeAssignment načítání. Dychtivá načítání je obvykle efektivnější, když se musí zobrazit související data. V tomto případě se zobrazí zadání kanceláře pro instruktory.
  • Když uživatel vybere instruktora, zobrazí se související Course entity. Entity Instructor a Course entity jsou v relaci M:N. Dychtivá načítání se používá pro Course entity a jejich související Department entity. V takovém případě můžou být samostatné dotazy efektivnější, protože jsou potřeba jenom kurzy pro vybraného instruktora. Tento příklad ukazuje, jak používat dychtivé načítání pro navigační vlastnosti v entitách, které jsou v navigačních vlastnostech.
  • Když uživatel vybere kurz, zobrazí se související data z Enrollments entity. Na předchozím obrázku se zobrazí jméno studenta a známka. Entity Course a Enrollment entity jsou v relaci 1:N.

Vytvoření modelu zobrazení

Na stránce instruktorů se zobrazují data ze tří různých tabulek. Model zobrazení je potřeba, který obsahuje tři vlastnosti představující tři tabulky.

Vytvořte Models/SchoolViewModels/InstructorIndexData.cs pomocí následujícího kódu:

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

Stránky instruktora uživatelského rozhraní

  • Postupujte podle pokynů na stránkách studentů vygenerování s následujícími výjimkami:

    • Vytvořte složku Pages/Instructors .
    • Používá Instructor se pro třídu modelu.
    • Místo vytvoření nové třídy použijte existující třídu kontextu.

Spusťte aplikaci a přejděte na stránku Instruktory.

Aktualizujte Pages/Instructors/Index.cshtml.cs následujícím kódem:

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

Metoda OnGetAsync přijímá volitelná směrovací data pro ID vybraného instruktora.

Prozkoumejte dotaz v Pages/Instructors/Index.cshtml.cs souboru:

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();

Kód určuje dychtivé načítání následujících vlastností navigace:

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

Následující kód se spustí, když je vybrán instruktor, to znamená id != null.

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

Vybraný instruktor se načte ze seznamu instruktorů v modelu zobrazení. Vlastnost modelu Courses zobrazení se načte s Course entitami z navigační vlastnosti vybraného instruktora Courses .

Metoda Where vrátí kolekci. V tomto případě filtr vybere jednu entitu, takže Single metoda se zavolá k převodu kolekce na jednu Instructor entitu. Entita Instructor poskytuje přístup k Course navigační vlastnosti.

Metoda Single se používá v kolekci, pokud má kolekce pouze jednu položku. Metoda Single vyvolá výjimku, pokud je kolekce prázdná nebo pokud existuje více než jedna položka. Alternativou je SingleOrDefault, která vrátí výchozí hodnotu, pokud je kolekce prázdná. Pro tento dotaz null ve výchozím vráceném dotazu.

Následující kód naplní vlastnost modelu Enrollments zobrazení při výběru kurzu:

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

Aktualizace indexové stránky instruktorů

Aktualizujte Pages/Instructors/Index.cshtml následujícím kódem.

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

Předchozí kód provede následující změny:

  • Aktualizuje direktivu page na @page "{id:int?}". "{id:int?}" je šablona trasy. Šablona trasy změní celočíselné řetězce dotazu v adrese URL pro směrování dat. Například kliknutím na odkaz Vybrat pro instruktora @page s pouze direktivou vznikne adresa URL podobná této:

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

    Pokud je @page "{id:int?}"direktiva stránky , adresa URL je: https://localhost:5001/Instructors/2

  • Přidá sloupec Office, který se zobrazí item.OfficeAssignment.Location jenom v případěitem.OfficeAssignment, že nemá hodnotu null. Vzhledem k tomu, že se jedná o relaci 1:0 nebo 1, nemusí existovat související entita OfficeAssignment.

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Přidá sloupec Kurzy, který zobrazuje kurzy vyučované jednotlivými instruktory. Další informace o této razor syntaxi najdete v tématu Explicitní přechod na řádku.

  • Přidá kód, který se dynamicky přidá class="table-success" do tr prvku vybraného instruktora a kurzu. Tím nastavíte barvu pozadí pro vybraný řádek pomocí třídy Bootstrap.

    string selectedRow = "";
    if (item.CourseID == Model.CourseID)
    {
        selectedRow = "table-success";
    }
    <tr class="@selectedRow">
    
  • Přidá nový hypertextový odkaz s popiskem Vybrat. Tento odkaz odešle id vybraného instruktora metodě Index a nastaví barvu pozadí.

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    
  • Přidá tabulku kurzů pro vybraného instruktora.

  • Přidá tabulku registrací studentů pro vybraný kurz.

Spusťte aplikaci a vyberte kartu Instruktori . Na stránce se Location zobrazí (office) ze související OfficeAssignment entity. Pokud OfficeAssignment je null, zobrazí se prázdná buňka tabulky.

Klikněte na odkaz Vybrat pro instruktora. Styl řádku se změní a kurzy přiřazené danému instruktorovi se zobrazí.

Výběrem kurzu zobrazíte seznam zaregistrovaných studentů a jejich známek.

Instruktoři indexovací stránky a vybraný kurz

Další kroky

V dalším kurzu se dozvíte, jak aktualizovat související data.

V tomto kurzu se dozvíte, jak číst a zobrazovat související data. Související data jsou data, která se EF Core načítají do navigačních vlastností.

Následující ilustrace ukazují dokončené stránky pro tento kurz:

Stránka rejstříku kurzů

Indexová stránka instruktorů

Dychtivé, explicitní a opožděné načítání

Existuje několik způsobů, jak EF Core načíst související data do navigačních vlastností entity:

  • Dychtivá načítání. Načítání dychtivosti spočívá v tom, že dotaz na jeden typ entity také načte související entity. Při čtení entity se načtou související data. Výsledkem je obvykle jeden dotaz spojení, který načte všechna potřebná data. EF Core vydá několik dotazů pro některé typy dychtivého načítání. Vydávání více dotazů může být efektivnější než obrovský jeden dotaz. Načítání s dychtivou zátěží je určeno metodami Include a ThenInclude metodami.

    Příklad načítání s dychtivou zátěží

    Načítání dychtivého načítání odesílá více dotazů, když je zahrnuta navigace v kolekci:

    • Jeden dotaz pro hlavní dotaz
    • Jeden dotaz pro každou "hranu" kolekce ve stromu zatížení.
  • Samostatné dotazy s Load: Data lze načíst v samostatných dotazech a EF Core "opraví" navigační vlastnosti. "Opravy" znamená, že EF Core automaticky naplní navigační vlastnosti. Samostatné dotazy s více než dychtivým načítáním Load jsou spíše explicitní než dychtivé načítání.

    Příklad samostatných dotazů

    Poznámka:EF Core Automaticky opravuje navigační vlastnosti na všechny ostatní entity, které byly dříve načteny do kontextové instance. I když data pro navigační vlastnost nejsou explicitně zahrnuta, může být vlastnost stále naplněna, pokud některé nebo všechny související entity byly dříve načteny.

  • Explicitní načítání. Při prvním čtení entity se související data nenačtou. Kód musí být zapsán pro načtení souvisejících dat v případě potřeby. Explicitní načítání pomocí samostatných dotazů vede k několika dotazům odesílaným do databáze. Při explicitním načtení kód určuje vlastnosti navigace, které se mají načíst. Použijte metodu Load k explicitnímu načítání. Příklad:

    Příklad explicitního načítání

  • Opožděné načítání. Při prvním čtení entity se související data nenačtou. Při prvním přístupu k navigační vlastnosti se automaticky načtou data potřebná pro tuto navigační vlastnost. Dotaz se odešle do databáze při prvním přístupu k navigační vlastnosti. Opožděné načítání může poškodit výkon, například když vývojáři používají vzory N+1, načítání nadřazeného objektu a výčet prostřednictvím podřízených položek.

Vytvoření stránek kurzu

Entita Course obsahuje navigační vlastnost, která obsahuje související Department entitu.

Course.Department

Zobrazení názvu přiřazeného oddělení pro kurz:

  • Načtěte související Department entitu do Course.Department navigační vlastnosti.
  • Získá název z Department vlastnosti entity Name .

Stránky kurzu pro generování uživatelského rozhraní

  • Postupujte podle pokynů na stránkách studentů uživatelského rozhraní s následujícími výjimkami:

    • Vytvořte složku Pages/Courses.
    • Používá Course se pro třídu modelu.
    • Místo vytvoření nové třídy použijte existující třídu kontextu.
  • Otevřete Pages/Courses/Index.cshtml.cs a prozkoumejte metodu OnGetAsync . Modul generování uživatelského rozhraní zadal dychtivého načítání pro Department navigační vlastnost. Metoda Include určuje dychtivé načítání.

  • Spusťte aplikaci a vyberte odkaz Kurzy . Ve sloupci oddělení se DepartmentIDzobrazí hodnota , která není užitečná.

Zobrazení názvu oddělení

Aktualizujte stránky/ kurzy/Index.cshtml.cs následujícím kódem:

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

Předchozí kód změní Course vlastnost na Courses a přidá AsNoTracking. AsNoTracking zvyšuje výkon, protože vrácené entity nejsou sledovány. Entity nemusí být sledovány, protože se neaktualizují v aktuálním kontextu.

Aktualizujte Pages/Courses/Index.cshtml následujícím kódem.

@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>

V kódu vygenerovaného uživatelského rozhraní byly provedeny následující změny:

  • Course Změna názvu vlastnosti na Courses.

  • Přidali jsme sloupec Číslo , který zobrazuje CourseID hodnotu vlastnosti. Ve výchozím nastavení se primární klíče nevygenerují, protože obvykle nejsou pro koncové uživatele smysluplné. V tomto případě je ale primární klíč smysluplný.

  • Změnili jsme sloupec Oddělení tak, aby zobrazoval název oddělení. Kód zobrazí Name vlastnost Department entity, která je načtena do Department navigační vlastnosti:

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

Spusťte aplikaci a výběrem karty Kurzy zobrazte seznam s názvy oddělení.

Stránka rejstříku kurzů

Metoda OnGetAsync načte související data s metodou Include . Metoda Select je alternativou, která načte pouze související data potřebná. Pro jednotlivé položky, jako je tomu u Department.Name jednotlivých položek, se používá funkce SQL INNER JOIN. U kolekcí používá jiný přístup k databázi, ale operátor Include v kolekcích.

Následující kód načte související data s metodou Select :

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

Předchozí kód nevrací žádné typy entit, proto se neprovádí žádné sledování. Další informace o sledování EF naleznete v tématu Sledování vs. Dotazy bez sledování.

Pomocná rutina CourseViewModel:

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

Úplný příklad najdete v souboru IndexSelect.cshtml a IndexSelect.cshtml.cs .

Vytvoření stránek instruktora

Tato část vygeneruje stránky instruktora a přidá související kurzy a registrace na stránku Index instruktorů.

Indexová stránka instruktorů

Tato stránka čte a zobrazuje související data následujícími způsoby:

  • Seznam instruktorů zobrazuje související data z OfficeAssignment entity (Office na předchozím obrázku). OfficeAssignment Entity Instructor jsou v relaci 1:0 nebo 1. Pro entity se používá dychtivé OfficeAssignment načítání. Dychtivá načítání je obvykle efektivnější, když se musí zobrazit související data. V tomto případě se zobrazí zadání kanceláře pro instruktory.
  • Když uživatel vybere instruktora, zobrazí se související Course entity. Entity Instructor a Course entity jsou v relaci M:N. Dychtivá načítání se používá pro Course entity a jejich související Department entity. V takovém případě můžou být samostatné dotazy efektivnější, protože jsou potřeba jenom kurzy pro vybraného instruktora. Tento příklad ukazuje, jak používat dychtivé načítání pro navigační vlastnosti v entitách, které jsou v navigačních vlastnostech.
  • Když uživatel vybere kurz, zobrazí se související data z Enrollments entity. Na předchozím obrázku se zobrazí jméno studenta a známka. Entity Course a Enrollment entity jsou v relaci 1:N.

Vytvoření modelu zobrazení

Na stránce instruktorů se zobrazují data ze tří různých tabulek. Model zobrazení je potřeba, který obsahuje tři vlastnosti představující tři tabulky.

Vytvořte SchoolViewModels/InstructorIndexData.cs pomocí následujícího kódu:

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

Stránky instruktora uživatelského rozhraní

  • Postupujte podle pokynů na stránkách studentů vygenerování s následujícími výjimkami:

    • Vytvořte složku Pages/Instructors .
    • Používá Instructor se pro třídu modelu.
    • Místo vytvoření nové třídy použijte existující třídu kontextu.

Pokud chcete zjistit, jak vypadá stránka vygenerovaná před aktualizací, spusťte aplikaci a přejděte na stránku instruktorů.

Aktualizujte Pages/Instructors/Index.cshtml.cs následujícím kódem:

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

Metoda OnGetAsync přijímá volitelná směrovací data pro ID vybraného instruktora.

Prozkoumejte dotaz v Pages/Instructors/Index.cshtml.cs souboru:

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();

Kód určuje dychtivé načítání následujících vlastností navigace:

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

Všimněte si opakování Include a ThenInclude metod pro CourseAssignments a Course. Toto opakování je nezbytné k určení dychtivého načítání dvou navigačních Course vlastností entity.

Následující kód se spustí při výběru instruktora (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);
}

Vybraný instruktor se načte ze seznamu instruktorů v modelu zobrazení. Vlastnost modelu Courses zobrazení se načte s Course entitami z navigační vlastnosti daného instruktora CourseAssignments .

Metoda Where vrátí kolekci. V tomto případě ale filtr vybere jednu entitu, takže Single metoda se zavolá k převodu kolekce na jednu Instructor entitu. Entita Instructor poskytuje přístup k CourseAssignments vlastnosti. CourseAssignments poskytuje přístup ke souvisejícím Course entitě.

Instruktor-kurzy m:M

Metoda Single se používá v kolekci, pokud má kolekce pouze jednu položku. Metoda Single vyvolá výjimku, pokud je kolekce prázdná nebo pokud existuje více než jedna položka. Alternativou je SingleOrDefault, která vrátí výchozí hodnotu (v tomto případě null), pokud je kolekce prázdná.

Následující kód naplní vlastnost modelu Enrollments zobrazení při výběru kurzu:

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

Aktualizace indexové stránky instruktorů

Aktualizujte Pages/Instructors/Index.cshtml následujícím kódem.

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

Předchozí kód provede následující změny:

  • Aktualizuje direktivu page od @page @page "{id:int?}" "{id:int?}" je šablona trasy. Šablona trasy změní celočíselné řetězce dotazu v adrese URL pro směrování dat. Například kliknutím na odkaz Vybrat pro instruktora @page s pouze direktivou vznikne adresa URL podobná této:

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

    Pokud je @page "{id:int?}"direktiva stránky , adresa URL je:

    https://localhost:5001/Instructors/2

  • Přidá sloupec Office, který se zobrazí item.OfficeAssignment.Location jenom v případěitem.OfficeAssignment, že nemá hodnotu null. Vzhledem k tomu, že se jedná o relaci 1:0 nebo 1, nemusí existovat související entita OfficeAssignment.

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Přidá sloupec Kurzy, který zobrazuje kurzy vyučované jednotlivými instruktory. Další informace o této razor syntaxi najdete v tématu Explicitní přechod na řádku.

  • Přidá kód, který se dynamicky přidá class="table-success" do tr prvku vybraného instruktora a kurzu. Tím nastavíte barvu pozadí pro vybraný řádek pomocí třídy Bootstrap.

    string selectedRow = "";
    if (item.CourseID == Model.CourseID)
    {
        selectedRow = "table-success";
    }
    <tr class="@selectedRow">
    
  • Přidá nový hypertextový odkaz s popiskem Vybrat. Tento odkaz odešle id vybraného instruktora metodě Index a nastaví barvu pozadí.

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    
  • Přidá tabulku kurzů pro vybraného instruktora.

  • Přidá tabulku registrací studentů pro vybraný kurz.

Spusťte aplikaci a vyberte kartu Instruktori . Na stránce se Location zobrazí (office) ze související OfficeAssignment entity. Pokud OfficeAssignment je null, zobrazí se prázdná buňka tabulky.

Klikněte na odkaz Vybrat pro instruktora. Styl řádku se změní a kurzy přiřazené danému instruktorovi se zobrazí.

Výběrem kurzu zobrazíte seznam zaregistrovaných studentů a jejich známek.

Instruktoři indexovací stránky a vybraný kurz

Použití jedné

Metoda Single může předat podmínku Where namísto samostatného Where volání metody:

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

Single Použití s podmínkou Where je otázkou osobní preference. Použití této Where metody neposkytuje žádné výhody.

Explicitní načítání

Aktuální kód určuje dychtivé načítání a Enrollments Students:

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();

Předpokládejme, že uživatelé zřídka chtějí vidět registrace v kurzu. V takovém případě by optimalizací bylo načíst pouze data registrace, pokud je to požadováno. V této části se aktualizuje, OnGetAsync aby používal explicitní načítání Enrollments a Students.

Aktualizujte Pages/Instructors/Index.cshtml.cs následujícím kódem.

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

Předchozí kód zahodí volání metody ThenInclude pro data o registraci a studentech. Pokud je vybraný kurz, explicitní načtení kódu načte:

  • Entity Enrollment pro vybraný kurz.
  • Entity Student pro každý Enrollment.

Všimněte si, že předchozí kód okomentuje .AsNoTracking(). Vlastnosti navigace lze explicitně načíst pouze pro sledované entity.

Otestujete aplikaci. Z pohledu uživatele se aplikace chová stejně jako předchozí verze.

Další kroky

V dalším kurzu se dozvíte, jak aktualizovat související data.

V tomto kurzu se související data čtou a zobrazují. Související data jsou data, která se EF Core načítají do navigačních vlastností.

Pokud narazíte na problémy, které nemůžete vyřešit, stáhněte nebo zobrazte dokončenou aplikaci. Pokyny ke stažení

Následující ilustrace ukazují dokončené stránky pro tento kurz:

Stránka rejstříku kurzů

Indexová stránka instruktorů

Existuje několik způsobů, jak EF Core načíst související data do navigačních vlastností entity:

  • Dychtivá načítání. Načítání dychtivosti spočívá v tom, že dotaz na jeden typ entity také načte související entity. Při čtení entity se načtou související data. Výsledkem je obvykle jeden dotaz spojení, který načte všechna potřebná data. EF Core vydá několik dotazů pro některé typy dychtivého načítání. Vydávání více dotazů může být efektivnější, než tomu bylo u některých dotazů v EF6, kdy byl jeden dotaz. Načítání s dychtivou zátěží je určeno metodami Include a ThenInclude metodami.

    Příklad načítání s dychtivou zátěží

    Načítání dychtivého načítání odesílá více dotazů, když je zahrnuta navigace v kolekci:

    • Jeden dotaz pro hlavní dotaz
    • Jeden dotaz pro každou "hranu" kolekce ve stromu zatížení.
  • Samostatné dotazy s Load: Data lze načíst v samostatných dotazech a EF Core "opraví" navigační vlastnosti. "Opravy" znamená, že EF Core automaticky naplní vlastnosti navigace. Samostatné dotazy s více než dychtivým načítáním Load jsou spíše explicitní než dychtivé načítání.

    Příklad samostatných dotazů

    Poznámka: EF Core Automaticky opravuje navigační vlastnosti na všechny ostatní entity, které byly dříve načteny do kontextové instance. I když data pro navigační vlastnost nejsou explicitně zahrnuta, může být vlastnost stále naplněna, pokud některé nebo všechny související entity byly dříve načteny.

  • Explicitní načítání. Při prvním čtení entity se související data nenačtou. Kód musí být zapsán pro načtení souvisejících dat v případě potřeby. Explicitní načítání pomocí samostatných dotazů vede k několika dotazům odeslaným do databáze. Při explicitním načtení kód určuje vlastnosti navigace, které se mají načíst. Použijte metodu Load k explicitnímu načítání. Příklad:

    Příklad explicitního načítání

  • Opožděné načítání. Opožděné načítání bylo přidáno do EF Core verze 2.1. Při prvním čtení entity se související data nenačtou. Při prvním přístupu k navigační vlastnosti se automaticky načtou data potřebná pro tuto navigační vlastnost. Dotaz se odešle do databáze při prvním přístupu k navigační vlastnosti.

  • Operátor Select načte pouze potřebná související data.

Vytvoření stránky kurzu, která zobrazuje název oddělení

Entita Course obsahuje navigační vlastnost, která obsahuje entitu Department . Entita Department obsahuje oddělení, ke kterému je kurz přiřazen.

Zobrazení názvu přiřazeného oddělení v seznamu kurzů:

  • Name Získá vlastnost z Department entity.
  • Entita Department pochází z Course.Department navigační vlastnosti.

Course.Department

Generování modelu kurzu

Postupujte podle pokynů vygenerování modelu studenta a použijte Course ho pro třídu modelu.

Předchozí příkaz vygeneruje Course model. Otevřete projekt v sadě Visual Studio.

Otevřete Pages/Courses/Index.cshtml.cs a prozkoumejte metodu OnGetAsync . Modul generování uživatelského rozhraní zadal dychtivého načítání pro Department navigační vlastnost. Metoda Include určuje dychtivé načítání.

Spusťte aplikaci a vyberte odkaz Kurzy . Ve sloupci oddělení se DepartmentIDzobrazí hodnota , která není užitečná.

Aktualizujte metodu OnGetAsync následujícím kódem:

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

Předchozí kód přidá AsNoTracking. AsNoTracking zvyšuje výkon, protože vrácené entity nejsou sledovány. Entity nejsou sledovány, protože se neaktualizují v aktuálním kontextu.

Aktualizujte Pages/Courses/Index.cshtml následujícím zvýrazněným kódem:

@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>

V kódu vygenerovaného uživatelského rozhraní byly provedeny následující změny:

  • Změnili jsme nadpis z indexu na Kurzy.

  • Přidali jsme sloupec Číslo , který zobrazuje CourseID hodnotu vlastnosti. Ve výchozím nastavení se primární klíče nevygenerují, protože obvykle nejsou pro koncové uživatele smysluplné. V tomto případě je ale primární klíč smysluplný.

  • Změnili jsme sloupec Oddělení tak, aby zobrazoval název oddělení. Kód zobrazí Name vlastnost Department entity, která je načtena do Department navigační vlastnosti:

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

Spusťte aplikaci a výběrem karty Kurzy zobrazte seznam s názvy oddělení.

Stránka rejstříku kurzů

Metoda OnGetAsync načte související data s metodou Include :

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

Operátor Select načte pouze potřebná související data. Pro jednotlivé položky, jako je tomu u Department.Name jednotlivých položek, se používá funkce SQL INNER JOIN. U kolekcí používá jiný přístup k databázi, ale operátor Include v kolekcích.

Následující kód načte související data s metodou Select :

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

Pomocná rutina CourseViewModel:

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

Úplný příklad najdete v souboru IndexSelect.cshtml a IndexSelect.cshtml.cs .

Vytvoření stránky instruktorů se zobrazenými kurzy a registracemi

V této části se vytvoří stránka Instruktory.

Indexová stránka instruktorů

Tato stránka čte a zobrazuje související data následujícími způsoby:

  • Seznam instruktorů zobrazuje související data z OfficeAssignment entity (Office na předchozím obrázku). OfficeAssignment Entity Instructor jsou v relaci 1:0 nebo 1. Pro entity se používá dychtivé OfficeAssignment načítání. Dychtivá načítání je obvykle efektivnější, když se musí zobrazit související data. V tomto případě se zobrazí zadání kanceláře pro instruktory.
  • Když uživatel vybere instruktora (Harui na předchozím obrázku), zobrazí se související Course entity. Entity Instructor a Course entity jsou v relaci M:N. Dychtivá načítání se používá pro Course entity a jejich související Department entity. V takovém případě můžou být samostatné dotazy efektivnější, protože jsou potřeba jenom kurzy pro vybraného instruktora. Tento příklad ukazuje, jak používat dychtivé načítání pro navigační vlastnosti v entitách, které jsou v navigačních vlastnostech.
  • Když uživatel vybere kurz (chemie na předchozím obrázku), zobrazí se související data z Enrollments entity. Na předchozím obrázku se zobrazí jméno studenta a známka. Entity Course a Enrollment entity jsou v relaci 1:N.

Vytvoření modelu zobrazení pro zobrazení indexu instruktora

Na stránce instruktorů se zobrazují data ze tří různých tabulek. Vytvoří se model zobrazení, který obsahuje tři entity představující tři tabulky.

Ve složce SchoolViewModels vytvořte InstructorIndexData.cs následující kód:

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

Generování modelu instruktora

Postupujte podle pokynů vygenerování modelu studenta a použijte Instructor ho pro třídu modelu.

Předchozí příkaz vygeneruje Instructor model. Spusťte aplikaci a přejděte na stránku instruktorů.

Nahraďte Pages/Instructors/Index.cshtml.cs následujícím kódem:

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

Metoda OnGetAsync přijímá volitelná směrovací data pro ID vybraného instruktora.

Prozkoumejte dotaz v Pages/Instructors/Index.cshtml.cs souboru:

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

Dotaz obsahuje dva prvky:

  • OfficeAssignment: Zobrazí se v zobrazení instruktorů.
  • CourseAssignments: To přináší kurzy, které se učí.

Aktualizace indexové stránky instruktorů

Aktualizujte Pages/Instructors/Index.cshtml následujícím kódem:

@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>

Předchozí kód provede následující změny:

  • Aktualizuje direktivu page od @page @page "{id:int?}" "{id:int?}" je šablona trasy. Šablona trasy změní celočíselné řetězce dotazu v adrese URL pro směrování dat. Například kliknutím na odkaz Vybrat pro instruktora @page s pouze direktivou vznikne adresa URL podobná této:

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

    Pokud je @page "{id:int?}"direktiva stránky , předchozí adresa URL je:

    http://localhost:1234/Instructors/2

  • Název stránky je Instruktor.

  • Přidali jsme sloupec Office , který se zobrazí item.OfficeAssignment.Location jenom v případě item.OfficeAssignment , že nemá hodnotu null. Vzhledem k tomu, že se jedná o relaci 1:0 nebo 1, nemusí existovat související entita OfficeAssignment.

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Přidali jsme sloupec Kurzy , který zobrazuje kurzy vyučované jednotlivými instruktory. Další informace o této razor syntaxi najdete v tématu Explicitní přechod na řádku.

  • Přidali jsme kód, který se dynamicky přidá class="success" do tr prvku vybraného instruktora. Tím nastavíte barvu pozadí pro vybraný řádek pomocí třídy Bootstrap.

    string selectedRow = "";
    if (item.CourseID == Model.CourseID)
    {
        selectedRow = "success";
    }
    <tr class="@selectedRow">
    
  • Přidali jsme nový hypertextový odkaz s popiskem Vybrat. Tento odkaz odešle id vybraného instruktora metodě Index a nastaví barvu pozadí.

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

Spusťte aplikaci a vyberte kartu Instruktori . Na stránce se Location zobrazí (office) ze související OfficeAssignment entity. Pokud má OfficeAssignment hodnotu null, zobrazí se prázdná buňka tabulky.

Klikněte na odkaz Vybrat . Styl řádku se změní.

Přidání kurzů vyučených vybraným instruktorem

Aktualizujte metodu OnGetAsync Pages/Instructors/Index.cshtml.cs následujícím kódem:

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

Přidat public int CourseID { get; set; }

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

Prozkoumejte aktualizovaný dotaz:

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();

Předchozí dotaz přidá Department entity.

Následující kód se spustí při výběru instruktora (id != null). Vybraný instruktor se načte ze seznamu instruktorů v modelu zobrazení. Vlastnost modelu Courses zobrazení se načte s Course entitami z navigační vlastnosti daného instruktora CourseAssignments .

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

Metoda Where vrátí kolekci. V předchozí Where metodě se vrátí pouze jedna Instructor entita. Metoda Single převede kolekci na jednu Instructor entitu. Entita Instructor poskytuje přístup k CourseAssignments vlastnosti. CourseAssignments poskytuje přístup ke souvisejícím Course entitě.

Instruktor-kurzy m:M

Metoda Single se používá v kolekci, pokud má kolekce pouze jednu položku. Metoda Single vyvolá výjimku, pokud je kolekce prázdná nebo pokud existuje více než jedna položka. Alternativou je SingleOrDefault, která vrátí výchozí hodnotu (v tomto případě null), pokud je kolekce prázdná. Použití SingleOrDefault v prázdné kolekci:

  • Výsledkem je výjimka (při pokusu Courses o vyhledání vlastnosti odkazu s hodnotou null).
  • Zpráva o výjimce by méně jasně označovala příčinu problému.

Následující kód naplní vlastnost modelu Enrollments zobrazení při výběru kurzu:

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

Na konec Pages/Instructors/Index.cshtmlRazor stránky přidejte následující kód:

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

Předchozí revize zobrazí seznam kurzů souvisejících s instruktorem při výběru instruktora.

Otestujete aplikaci. Klikněte na odkaz Vybrat na stránce instruktorů.

Zobrazení dat studentů

V této části se aplikace aktualizuje, aby zobrazovala data studentů pro vybraný kurz.

Aktualizujte dotaz v OnGetAsync metodě Pages/Instructors/Index.cshtml.cs následujícím kódem:

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();

Aktualizujte Pages/Instructors/Index.cshtml. Na konec souboru přidejte následující kód:


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

Předchozí revize zobrazí seznam studentů, kteří jsou zaregistrovaní ve vybraném kurzu.

Aktualizujte stránku a vyberte instruktora. Výběrem kurzu zobrazíte seznam zaregistrovaných studentů a jejich známek.

Instruktoři indexovací stránky a vybraný kurz

Použití jedné

Metoda Single může předat podmínku Where namísto samostatného Where volání metody:

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

Předchozí Single přístup neposkytuje žádné výhody oproti používání Where. Někteří vývojáři dávají přednost Single stylu přístupu.

Explicitní načítání

Aktuální kód určuje dychtivé načítání a Enrollments Students:

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();

Předpokládejme, že uživatelé zřídka chtějí vidět registrace v kurzu. V takovém případě by optimalizací bylo načíst pouze data registrace, pokud je to požadováno. V této části se aktualizuje, OnGetAsync aby používal explicitní načítání Enrollments a Students.

OnGetAsync Aktualizujte následující kód:

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

Předchozí kód zahodí volání metody ThenInclude pro data o registraci a studentech. Pokud je vybraný kurz, zvýrazněný kód načte:

  • Entity Enrollment pro vybraný kurz.
  • Entity Student pro každý Enrollment.

Všimněte si předchozího komentáře .AsNoTracking()ke kódu . Vlastnosti navigace lze explicitně načíst pouze pro sledované entity.

Otestujete aplikaci. Z pohledu uživatelů se aplikace chová stejně jako předchozí verze.

V dalším kurzu se dozvíte, jak aktualizovat související data.

Další materiály