Kurz: Čtení souvisejících dat – ASP.NET MVC pomocí EF Core
V předchozím kurzu jste dokončili datový model School. V tomto kurzu si přečtete a zobrazíte související data – to znamená data, která Entity Framework načte do navigačních vlastností.
Následující ilustrace znázorňují stránky, se kterými budete pracovat.
V tomto kurzu se naučíte:
- Naučte se načítat související data.
- Vytvoření stránky Kurzy
- Vytvoření stránky instruktorů
- Informace o explicitním načítání
Požadavky
Naučte se načítat související data.
Software ORM (Object-Relational Mapping), jako je Entity Framework, může načíst související data do navigačních vlastností entity:
Načítání dychtivých dat: Při čtení entity se spolu s ní načtou související data. Výsledkem je obvykle jeden dotaz spojení, který načte všechna potřebná data. Zadáním dychtivého načítání do Entity Framework Core pomocí metod
Include
aThenInclude
metod.Některá data můžete načíst v samostatnýchdotazch Ef tedy automaticky přidá samostatně načtené entity, do kterých patří do navigačních vlastností dříve načtených entit. Pro dotaz, který načte související data, můžete použít
Load
metodu místo metody, která vrací seznam nebo objekt, napříkladToList
neboSingle
.Explicitní načtení: Při prvním čtení entity se související data nenačtou. Napíšete kód, který načte související data, pokud je to potřeba. Stejně jako v případě dychtivého načítání samostatnými dotazy má explicitní načtení za následek více dotazů odesílaných do databáze. Rozdíl je v tom, že při explicitním načtení určuje kód vlastnosti navigace, které se mají načíst. V Entity Framework Core 1.1 můžete použít 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 pokusu o přístup k navigační vlastnosti se však automaticky načtou data potřebná pro tuto navigační vlastnost. Dotaz se odešle do databáze pokaždé, když se pokusíte získat data z navigační vlastnosti poprvé. Entity Framework Core 1.0 nepodporuje opožděné načítání.
Důležité informace o výkonu
Pokud víte, že potřebujete související data pro každou načtenou entitu, načítání dychtivosti často nabízí nejlepší výkon, protože jeden dotaz odeslaný do databáze je obvykle efektivnější než samostatné dotazy pro každou načtenou entitu. Předpokládejme například, že každé oddělení má deset souvisejících kurzů. Dychtivá načtení všech souvisejících dat by vedlo k jedinému dotazu (spojení) a jediné odezvě databáze. Samostatný dotaz pro kurzy pro každé oddělení by vedlo k jedenácti odezvě do databáze. Dodatečné odezvy databáze jsou zvláště škodlivé pro výkon, pokud je latence vysoká.
Na druhou stranu v některých scénářích jsou samostatné dotazy efektivnější. Dychtivá načítání všech souvisejících dat v jednom dotazu může způsobit generování velmi složitého spojení, které SQL Server nemůže efektivně zpracovat. Nebo pokud potřebujete získat přístup k navigačním vlastnostem entity pouze pro podmnožinu sady entit, které zpracováváte, můžou samostatné dotazy fungovat lépe, protože dychtivé načítání všeho předem by načítalo více dat, než potřebujete. Pokud je výkon kritický, je nejlepší otestovat výkon oběma způsoby, aby byla nejlepší volbou.
Vytvoření stránky Kurzy
Entita Course
obsahuje navigační vlastnost, která obsahuje entitu Department
oddělení, ke kterému je kurz přiřazen. Pokud chcete zobrazit název přiřazeného oddělení v seznamu kurzů, musíte vlastnost získat Name
z Department
entity, která je v Course.Department
navigační vlastnosti.
Vytvořte kontroler pojmenovaný CoursesController
pro Course
typ entity pomocí stejných možností pro kontroler MVC se zobrazeními pomocí nástroje Entity Framework scaffolder, který jste provedli dříve pro danou entitu StudentsController
, jak je znázorněno na následujícím obrázku:
Otevřete CoursesController.cs
a prozkoumejte metodu Index
. Automatické generování uživatelského rozhraní pomocí metody určilo dychtivé načítání pro Department
navigační vlastnost Include
.
Nahraďte metodu Index
následujícím kódem, který používá vhodnější název pro IQueryable
entity kurzu (courses
místo schoolContext
):
public async Task<IActionResult> Index()
{
var courses = _context.Courses
.Include(c => c.Department)
.AsNoTracking();
return View(await courses.ToListAsync());
}
Otevřete Views/Courses/Index.cshtml
a nahraďte kód šablony následujícím kódem. Změny jsou zvýrazněné:
@model IEnumerable<ContosoUniversity.Models.Course>
@{
ViewData["Title"] = "Courses";
}
<h2>Courses</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Vygenerovaný kód jste provedli 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ý a chcete ho zobrazit.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í.
Vytvoření stránky instruktorů
V této části vytvoříte kontroler a zobrazíte entitu Instructor
, abyste zobrazili stránku instruktorů:
Tato stránka čte a zobrazuje související data následujícími způsoby:
Seznam instruktorů zobrazuje související data z
OfficeAssignment
dané entity.OfficeAssignment
EntityInstructor
jsou v relaci 1:0 nebo 1. Pro entity použijete dychtivé načítáníOfficeAssignment
. Jak bylo vysvětleno dříve, načítání dychtivosti je obvykle efektivnější, když potřebujete související data pro všechny načtené řádky primární tabulky. V takovém případě chcete zobrazit zadání kanceláře pro všechny zobrazené instruktory.Když uživatel vybere instruktora, zobrazí se související
Course
entity. EntityInstructor
aCourse
entity jsou v relaci M:N. Pro entity a souvisejícíDepartment
entity použijete dychtivé načítáníCourse
. V takovém případě můžou být samostatné dotazy efektivnější, protože potřebujete kurzy jenom pro vybraného instruktora. Tento příklad však ukazuje, jak používat dychtivé načítání vlastností navigace v rámci entit, které jsou samy ve vlastnostech navigace.Když uživatel vybere kurz, zobrazí se související data ze
Enrollments
sady entit. EntityCourse
aEnrollment
entity jsou v relaci 1:N. Použijete samostatné dotazy proEnrollment
entity a jejich souvisejícíStudent
entity.
Vytvoření modelu zobrazení pro zobrazení indexu instruktora
Na stránce Instruktori se zobrazují data ze tří různých tabulek. Proto vytvoříte model zobrazení, který obsahuje tři vlastnosti, z nichž každá obsahuje data pro jednu z tabulek.
Ve složce SchoolViewModels vytvořte InstructorIndexData.cs
a nahraďte existující kód následujícím kódem:
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; }
}
}
Vytvoření kontroleru a zobrazení instruktora
Vytvořte kontroler instruktorů s akcemi EF pro čtení a zápis, jak je znázorněno na následujícím obrázku:
Otevřete InstructorsController.cs
a přidejte příkaz using pro obor názvů ViewModels:
using ContosoUniversity.Models.SchoolViewModels;
Nahraďte metodu Index následujícím kódem, který docítí načítání souvisejících dat a vloží je do modelu zobrazení.
public async Task<IActionResult> Index(int? id, int? courseID)
{
var viewModel = new InstructorIndexData();
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
return View(viewModel);
}
Metoda přijímá volitelná data směrování (id
) a parametr řetězce dotazu (courseID
), který poskytuje hodnoty ID vybraného instruktora a vybraného kurzu. Parametry poskytují hypertextové odkazy na stránce.
Kód začíná vytvořením instance modelu zobrazení a jeho vložením do seznamu instruktorů. Kód určuje dychtivé načítání pro Instructor.OfficeAssignment
vlastnosti navigace a Instructor.CourseAssignments
vlastnosti navigace. V rámci CourseAssignments
této vlastnosti se vlastnost Course
načte a v rámci této Enrollments
vlastnosti se načtou a Department
v rámci každé Enrollment
entity Student
se vlastnost načte.
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Vzhledem k tomu, že zobrazení vždy vyžaduje entitu OfficeAssignment
, je efektivnější ji načíst ve stejném dotazu. Entity kurzu se vyžadují, když je na webové stránce vybrán instruktor, takže jeden dotaz je lepší než více dotazů, pouze pokud se stránka zobrazuje častěji s vybraným kurzem než bez.
Kód se CourseAssignments
opakuje a Course
protože potřebujete dvě vlastnosti z Course
. První řetězec ThenInclude
volání získá CourseAssignment.Course
, Course.Enrollments
a Enrollment.Student
.
Další informace o zahrnutí více úrovní souvisejících dat najdete tady.
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
V tomto okamžiku v kódu by další ThenInclude
byl pro navigační vlastnosti Student
, které nepotřebujete. Ale volání Include
začíná s vlastnostmi Instructor
, takže musíte znovu procházet řetěz, tentokrát zadat Course.Department
místo Course.Enrollments
.
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Následující kód se spustí, když byl vybrán instruktor. Vybraný instruktor se načte ze seznamu instruktorů v modelu zobrazení. Vlastnost modelu Courses
zobrazení se pak načte s Course
entitami z navigační vlastnosti daného instruktora CourseAssignments
.
if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
Metoda Where
vrátí kolekci, ale v tomto případě kritéria předaná této metodě způsobí vrácení pouze jedné entity instruktora. Metoda Single
převede kolekci na jednu Instructor
entitu, která poskytuje přístup k vlastnosti dané entity CourseAssignments
. Vlastnost CourseAssignments
obsahuje CourseAssignment
entity, ze kterých chcete pouze související Course
entity.
Metodu Single
v kolekci použijete, když víte, že kolekce bude obsahovat pouze jednu položku. Metoda Single
vyvolá výjimku, pokud je kolekce předána do 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á. V tomto případě by však stále došlo k výjimce (z pokusu o nalezení Courses
vlastnosti u odkazu s hodnotou null) a zpráva o výjimce by méně jasně označovala příčinu problému. Při volání Single
metody můžete také předat podmínku Where namísto samostatného volání Where
metody:
.Single(i => i.ID == id.Value)
Místo:
.Where(i => i.ID == id.Value).Single()
Pokud jste vybrali kurz, vybraný kurz se načte ze seznamu kurzů v modelu zobrazení. Vlastnost modelu Enrollments
zobrazení se pak načte s entitami registrace z navigační Enrollments
vlastnosti daného kurzu.
if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
Sledování vs. bez sledování
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í.
Úprava zobrazení indexu instruktora
Nahraďte Views/Instructors/Index.cshtml
kód šablony následujícím kódem. Změny jsou zvýrazněné.
@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Instructors)
{
string selectedRow = "";
if (item.ID == (int?)ViewData["InstructorID"])
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @course.Course.Title <br />
}
</td>
<td>
<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Instructors)
{
string selectedRow = "";
if (item.ID == (int?)ViewData["InstructorID"])
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @course.Course.Title <br />
}
</td>
<td>
<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
V existujícím kódu jste provedli následující změny:
Změna třídy modelu na
InstructorIndexData
.Změnili jsme název stránky z indexu na instruktory.
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 najdete v části Razor Explicitní přechod řádku článku syntaxe.
Přidání kódu, který podmíněně přidá třídu CSS bootstrap do
tr
prvku vybraného instruktora. Tato třída nastaví barvu pozadí pro vybraný řádek.Přidali jsme nový hypertextový odkaz s popiskem Vybrat bezprostředně před ostatní odkazy v každém řádku, což způsobí odeslání ID vybraného instruktora do
Index
metody.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Spusťte aplikaci a vyberte kartu Instruktori . Na stránce se zobrazí vlastnost Umístění souvisejících entit OfficeAssignment a prázdná buňka tabulky, pokud neexistuje žádná související entita OfficeAssignment.
Views/Instructors/Index.cshtml
Do souboru za konečný prvek tabulky (na konci souboru) přidejte následující kód. Tento kód zobrazí seznam kurzů souvisejících s instruktorem, když je vybrán instruktor.
@if (Model.Courses != null)
{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>
@foreach (var item in Model.Courses)
{
string selectedRow = "";
if (item.CourseID == (int?)ViewData["CourseID"])
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}
</table>
}
Tento kód přečte Courses
vlastnost modelu zobrazení, aby se zobrazil seznam kurzů. Poskytuje také hypertextový odkaz Select , který odešle ID vybraného kurzu metodě Index
akce.
Aktualizujte stránku a vyberte instruktora. Teď uvidíte mřížku, která zobrazuje kurzy přiřazené vybranému instruktorovi a pro každý kurz uvidíte název přiřazeného oddělení.
Za blok kódu, který jste právě přidali, přidejte následující kód. Zobrazí se seznam studentů, kteří jsou zaregistrovaní v kurzu při výběru tohoto kurzu.
@if (Model.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
Tento kód přečte Enrollments
vlastnost modelu zobrazení, aby se zobrazil seznam studentů zaregistrovaných v kurzu.
Aktualizujte stránku znovu a vyberte instruktora. Pak vyberte kurz, abyste viděli seznam zaregistrovaných studentů a jejich známek.
Informace o explicitním načítání
Po načtení seznamu instruktorů jste zadali dychtivé InstructorsController.cs
načítání pro CourseAssignments
navigační vlastnost.
Předpokládejme, že jste očekávali, že uživatelé budou chtít jenom zřídka vidět registrace ve vybraném instruktoru a kurzu. V takovém případě můžete chtít načíst data registrace jenom v případě, že je požadována. Pokud chcete vidět příklad, jak provést explicitní načítání, nahraďte Index
metodu následujícím kódem, který odebere dychtivé načítání Enrollments
a načte danou vlastnost explicitně. Změny kódu jsou zvýrazněné.
public async Task<IActionResult> Index(int? id, int? courseID)
{
var viewModel = new InstructorIndexData();
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
}
viewModel.Enrollments = selectedCourse.Enrollments;
}
return View(viewModel);
}
Nový kód zahodí ThenInclude
volání metody pro data registrace z kódu, který načítá entity instruktora. Také klesá AsNoTracking
. Pokud je vybrán instruktor a kurz, zvýrazněný kód načte Enrollment
entity pro vybraný kurz a Student
entity pro každý Enrollment
z nich .
Spusťte aplikaci, přejděte na stránku Index instruktorů a uvidíte žádný rozdíl v tom, co se na stránce zobrazuje, i když jste změnili způsob načtení dat.
Získání kódu
Stáhněte nebo zobrazte dokončenou aplikaci.
Další kroky
V tomto kurzu se naučíte:
- Naučili jste se načíst související data.
- Vytvoření stránky Kurzy
- Vytvoření stránky instruktorů
- Dozvěděli jste se o explicitním načítání
V dalším kurzu se dozvíte, jak aktualizovat související data.