Čá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:
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.
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ímLoad
jsou spíše explicitní než dychtivé načítání.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: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.
Zobrazení názvu přiřazeného oddělení pro kurz:
- Načtěte související
Department
entitu doCourse.Department
navigační vlastnosti. - Získá název z
Department
vlastnosti entityName
.
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 metoduOnGetAsync
. Modul generování uživatelského rozhraní zadal dychtivého načítání proDepartment
navigační vlastnost. MetodaInclude
určuje dychtivé načítání.Spusťte aplikaci a vyberte odkaz Kurzy . Ve sloupci oddělení se
DepartmentID
zobrazí 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 naCourses
.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
vlastnostDepartment
entity, která je načtena doDepartment
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í.
Načtení souvisejících dat pomocí možnosti Vybrat
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ů.
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
EntityInstructor
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. EntityInstructor
aCourse
entity jsou v relaci M:N. Dychtivá načítání se používá proCourse
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. EntityCourse
aEnrollment
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"
dotr
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.
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:
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
aThenInclude
metodami.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ímLoad
jsou spíše explicitní než dychtivé načítání.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: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.
Zobrazení názvu přiřazeného oddělení pro kurz:
- Načtěte související
Department
entitu doCourse.Department
navigační vlastnosti. - Získá název z
Department
vlastnosti entityName
.
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 metoduOnGetAsync
. Modul generování uživatelského rozhraní zadal dychtivého načítání proDepartment
navigační vlastnost. MetodaInclude
určuje dychtivé načítání.Spusťte aplikaci a vyberte odkaz Kurzy . Ve sloupci oddělení se
DepartmentID
zobrazí 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 naCourses
.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
vlastnostDepartment
entity, která je načtena doDepartment
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í.
Načtení souvisejících dat pomocí možnosti Vybrat
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ů.
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
EntityInstructor
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. EntityInstructor
aCourse
entity jsou v relaci M:N. Dychtivá načítání se používá proCourse
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. EntityCourse
aEnrollment
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ě.
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"
dotr
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.
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:
Dychtivá, explicitní a opožděná načítání souvisejících dat
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
aThenInclude
metodami.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ímLoad
jsou spíše explicitní než dychtivé načítání.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: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 zDepartment
entity.- Entita
Department
pochází zCourse.Department
navigační vlastnosti.
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 DepartmentID
zobrazí 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
vlastnostDepartment
entity, která je načtena doDepartment
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í.
Načtení souvisejících dat pomocí možnosti Vybrat
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.
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
EntityInstructor
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. EntityInstructor
aCourse
entity jsou v relaci M:N. Dychtivá načítání se používá proCourse
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. EntityCourse
aEnrollment
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"
dotr
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ě.
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.cshtml
Razor 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.
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.