Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
V předchozím tutoriálu jste implementovali sadu webových stránek pro základní operace CRUD pro entity studentů. V tomto kurzu přidáte funkce řazení, filtrování a stránkování na stránku Index studentů. Také vytvoříte stránku, která provádí jednoduché seskupení.
Následující obrázek ukazuje, jak bude stránka vypadat po dokončení. Záhlaví sloupců jsou odkazy, na které uživatel může kliknout a seřadit podle tohoto sloupce. Opakovaným kliknutím na záhlaví sloupce se přepíná mezi vzestupným a sestupným pořadím řazení.
V tomto kurzu se naučíte:
- Přidejte odkazy pro řazení sloupců
- Přidání vyhledávacího pole
- Přidání stránkování do indexu Studentů
- Přidání stránkování do metody Index
- Přidání stránkovacího odkazu
- Vytvoření stránky O aplikaci
Požadavky
Přidejte odkazy pro řazení sloupců
Pokud chcete přidat řazení na stránku Index studenta, změníte Index
metodu kontroleru Students a přidáte kód do zobrazení Index studenta.
Přidání funkce řazení do metody indexu
V StudentsController.cs
příkazu nahraďte metodu Index
následujícím kódem:
public async Task<IActionResult> Index(string sortOrder)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}
Tento kód obdrží sortOrder
parametr z řetězce dotazu v adrese URL. Hodnotu řetězce dotazu poskytuje ASP.NET Core MVC jako parametr metody akce. Parametr bude řetězec, který je buď "Name" nebo "Date", volitelně následuje podtržítko a řetězec "desc" k určení sestupného pořadí. Výchozí pořadí řazení je vzestupné.
Při prvním vyžádání indexové stránky neexistuje žádný řetězec dotazu. Studenti jsou zobrazeni ve vzestupném pořadí podle příjmení, což je výchozí nastavení, které je zajištěno ve výchozím případě v příkazu switch
. Když uživatel klikne na hypertextový odkaz záhlaví sloupce, v řetězci dotazu se zobrazí příslušná sortOrder
hodnota.
ViewData
Dva prvky (NameSortParm a DateSortParm) se používají v zobrazení ke konfiguraci hypertextových odkazů záhlaví sloupců s příslušnými řetězcovými hodnotami dotazu.
public async Task<IActionResult> Index(string sortOrder)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}
Jedná se o ternární příkazy. První určuje, že pokud sortOrder
je parametr null nebo prázdný, nameSortParm by měl být nastaven na "name_desc". Jinak by měl být nastaven na prázdný řetězec. Tyto dva příkazy umožňují zobrazení nastavit hypertextové odkazy záhlaví sloupců následujícím způsobem:
Aktuální pořadí řazení | Hypertextový odkaz na příjmení | Hypertextový odkaz na datum |
---|---|---|
Příjmení vzestupně | sestupný | ascending |
Příjmení sestupně | vzestupný | vzestupný |
Datum vzestupně | vzestupně | sestupující |
Datum sestupně | ascending | vzestupný |
Metoda používá LINQ to Entities k určení sloupce, podle který se má seřadit. Kód vytvoří proměnnou IQueryable
před příkazem switch, upraví ho v příkazu switch a zavolá ToListAsync
metodu za příkazem switch
. Při vytváření a úpravě IQueryable
proměnných se do databáze neposílají žádné dotazy. Dotaz se nespustí, dokud nepřeveďte IQueryable
objekt do kolekce voláním metody, jako ToListAsync
je . Výsledkem tohoto kódu je proto jeden dotaz, který se nespustí, dokud se return View
příkaz nespustí.
Tento kód by se mohl stát rozvláčným při velkém počtu sloupců.
Poslední kurz v této sérii ukazuje, jak napsat kód, který umožňuje předat název OrderBy
sloupce v řetězcové proměnné.
Přidat hypertextové odkazy na záhlaví sloupců v zobrazení Index studentů
Nahraďte kód v Views/Students/Index.cshtml
, následujícím kódem pro přidání hypertextových odkazů záhlaví sloupce. Změněné čáry jsou zvýrazněné.
@model IEnumerable<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]">@Html.DisplayNameFor(model => model.LastName)</a>
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]">@Html.DisplayNameFor(model => model.EnrollmentDate)</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<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>
Tento kód používá informace ve ViewData
vlastnostech k nastavení hypertextových odkazů s příslušnými hodnotami řetězce dotazu.
Spusťte aplikaci, vyberte kartu Studenti a kliknutím na záhlaví sloupců Příjmení a Datum registrace ověřte, že řazení funguje.
Přidání vyhledávacího pole
Pokud chcete přidat filtrování na stránku Index studentů, přidáte do zobrazení textové pole a tlačítko odeslat a provedete odpovídající změny v Index
metodě. Textové pole umožňuje zadat řetězec k vyhledání v polích jméno a příjmení.
Přidání funkce filtrování do metody Index
V StudentsController.cs
příkazu nahraďte metodu Index
následujícím kódem (změny jsou zvýrazněny).
public async Task<IActionResult> Index(string sortOrder, string searchString)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}
K metodě Index
jste přidali parametr searchString
. Hodnota vyhledávacího řetězce se získává z textového pole, které přidáte do Index View. Přidali jste také do příkazu LINQ klauzuli where, která vybere jenom studenty, jejichž křestní jméno nebo příjmení obsahuje hledaný řetězec. Příkaz, který přidá klauzuli where, se provede pouze v případě, že je hodnota, kterou chcete vyhledat.
Poznámka:
Tady voláte metodu Where
IQueryable
na objektu a filtr se zpracuje na serveru. V některých scénářích můžete metodu Where
volat jako metodu rozšíření v kolekci v paměti. (Předpokládejme například, že změníte odkaz _context.Students
tak, že místo odkazování na EF DbSet
bude odkazovat na metodu úložiště, která vrací kolekci IEnumerable
.) Výsledek by obvykle byl stejný, ale v některých případech se může lišit.
Například implementace metody Contains
v rozhraní .NET Framework provádí ve výchozím nastavení porovnání s rozlišováním velkých a malých písmen, ale v SQL Serveru je to určeno kolací instance SQL Serveru. Toto nastavení je ve výchozím stavu nerozlišující malá a velká písmena. Metodu ToUpper
můžete volat tak, aby test explicitně nerozlišil malá a velká písmena: Where(s>=s.LastName.ToUpper(). Contains(searchString.Toupper()) To zajistí, aby výsledky zůstaly stejné, pokud později změníte kód tak, aby používal úložiště, které vrací IEnumerable
kolekci místo objektu IQueryable
. (Při volání Contains
metody v IEnumerable
kolekci získáte implementaci rozhraní .NET Framework; když ji zavoláte na IQueryable
objektu, získáte implementaci zprostředkovatele databáze.) Pro toto řešení je ale snížení výkonu. Kód ToUpper
by vložil funkci do klauzule WHERE příkazu TSQL SELECT. Tím by se zabránilo použití indexu optimalizátoru. Vzhledem k tomu, že SQL se většinou instaluje bez rozlišování velkých a malých písmen, je nejlepší se kódu ToUpper
vyhnout, dokud nemigrujete do úložiště dat bez rozlišování velkých a malých písmen.
Přidat vyhledávací pole do zobrazení studentského indexu
V Views/Student/Index.cshtml
přidejte zvýrazněný kód bezprostředně před počáteční značku tabulky, pokud chcete vytvořit titulek, textové pole a tlačítko Hledat.
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-action="Index" method="get">
<div class="form-actions no-color">
<p>
<label>Find by name: <input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" /></label>
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>
<table class="table">
Tento kód používá <form>
pomocník značek k přidání vyhledávacího textového pole a tlačítka. Ve výchozím nastavení <form>
pomocník značky odesílá data formuláře pomocí POST, což znamená, že parametry se předávají v textu zprávy HTTP a ne v adrese URL jako dotazovací řetězce. Když zadáte HTTP GET, data formuláře se v adrese URL předávají jako parametry dotazu, což uživatelům umožňuje si adresu URL uložit do záložek. Pokyny W3C doporučují, abyste měli použít get, když akce nemá za následek aktualizaci.
Spusťte aplikaci, vyberte kartu Studenti , zadejte hledaný řetězec a kliknutím na Hledat ověřte, že filtrování funguje.
Všimněte si, že adresa URL obsahuje hledaný řetězec.
http://localhost:5813/Students?SearchString=an
Pokud si tuto stránku označíte záložkou, zobrazí se filtrovaný seznam při použití této záložky. Přidání method="get"
ke form
značce je to, co způsobilo vygenerování řetězce dotazu.
Pokud v této fázi kliknete na odkaz pro řazení záhlaví sloupce, ztratíte hodnotu filtru, kterou jste zadali do vyhledávacího pole. Opravíte to v další části.
Přidání stránkování do indexu Studentů
Pokud chcete přidat stránkování na stránku Index studentů, vytvoříte PaginatedList
třídu, která používá Skip
a Take
uvádí příkazy k filtrování dat na serveru místo toho, abyste vždy načítá všechny řádky tabulky. Pak provedete další změny v Index
metodě a přidáte do zobrazení stránkovací tlačítka Index
. Následující obrázek znázorňuje stránkovací tlačítka.
Ve složce projektu vytvořte PaginatedList.cs
a pak nahraďte kód šablony následujícím kódem.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}
Metoda CreateAsync
v tomto kódu přijímá velikost stránky a číslo stránky a použije příslušné příkazy Skip
a Take
na IQueryable
. Při volání ToListAsync
na IQueryable
vrátí seznam obsahující pouze požadovanou stránku. Vlastnosti HasPreviousPage
a HasNextPage
lze použít k povolení nebo zakázání stránkovacího tlačítka předchozí a další.
Metoda CreateAsync
se používá místo konstruktoru k vytvoření objektu PaginatedList<T>
, protože konstruktory nemohou spustit asynchronní kód.
Přidejte stránkování do metody Index
V StudentsController.cs
, nahraďte metodu Index
následujícím kódem.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));
}
Tento kód přidá do podpisu metody parametr čísla stránky, aktuální parametr pořadí řazení a aktuální parametr filtru.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
Při prvním zobrazení stránky nebo pokud uživatel neklikli na stránkovací nebo seřazovací odkaz, budou mít všechny parametry hodnotu null. Pokud kliknete na stránkovací odkaz, proměnná stránky bude obsahovat číslo stránky, které se má zobrazit.
Element ViewData
s názvem CurrentSort poskytuje zobrazení aktuální pořadí řazení, protože toto pořadí musí být součástí stránkovacích odkazů, aby při stránkování zůstalo stejné.
Element ViewData
s názvem CurrentFilter poskytuje zobrazení s aktuálním řetězcem filtru. Tato hodnota musí být zahrnuta ve stránkovacích odkazech, aby bylo možné zachovat nastavení filtru během stránkování, a při opětovném zobrazení stránky se musí obnovit do textového pole.
Pokud se během stránkování změní hledaný řetězec, musí se stránka resetovat na hodnotu 1, protože nový filtr může vést k zobrazení různých dat. Hledaný řetězec se změní při zadání hodnoty do textového pole a stisknutí tlačítka Odeslat. V takovém případě searchString
parametr nemá hodnotu null.
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
Na konci metody Index
převede metoda PaginatedList.CreateAsync
dotaz studenta na jednu stránku se studenty v typu kolekce, která podporuje stránkování. Tato jediná stránka studentů je pak předána na zobrazení.
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));
Metoda PaginatedList.CreateAsync
přebírá číslo stránky. Dvě otazníky představují tzv. operátor nulového sjednocení. Operátor nulového sjednocení definuje výchozí hodnotu pro typ s možnou hodnotou null; výraz (pageNumber ?? 1)
znamená, že vrátí hodnotu pageNumber
, pokud má hodnotu, nebo vrátí hodnotu 1, pokud pageNumber
má hodnotu null.
Přidejte stránkovací odkazy
Nahraďte Views/Students/Index.cshtml
stávající kód následujícím kódem. Změny jsou zvýrazněné.
@model PaginatedList<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-action="Index" method="get">
<div class="form-actions no-color">
<p>
<label>Find by name: <input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" /></label>
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Last Name</a>
</th>
<th>
First Name
</th>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Enrollment Date</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<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>
@{
var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex + 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @nextDisabled">
Next
</a>
Příkaz @model
v horní části stránky určuje, že zobrazení nyní získá PaginatedList<T>
objekt místo objektu List<T>
.
Odkazy v záhlaví sloupce používají řetězec dotazu k předání aktuálního vyhledávacího řetězce řadiči, aby mohl uživatel třídit výsledky filtrování.
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter ="@ViewData["CurrentFilter"]">Enrollment Date</a>
Stránkovací tlačítka jsou zobrazována pomocí tag helperů.
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>
Spusťte aplikaci a přejděte na stránku Studenti.
Klikněte na stránkovací odkazy v různých pořadích řazení a ujistěte se, že stránkování funguje. Potom zadejte hledaný řetězec a zkuste stránkování zopakovat, abyste ověřili, že stránkování funguje s řazením a filtrováním správně.
Vytvoření stránky O aplikaci
Na stránce Informace o webu Contoso University se zobrazí počet studentů, kteří se zaregistrovali ke každému datu registrace. To vyžaduje seskupování a jednoduché výpočty skupin. Uděláte to takto:
- Vytvořte třídu modelu zobrazení pro data, která potřebujete předat do zobrazení.
- Vytvořte v kontroleru metodu Home About.
- Vytvořte zobrazení O aplikaci.
Vytvoření modelu zobrazení
Ve složce Models vytvořte složku SchoolViewModels.
Do nové složky přidejte soubor EnrollmentDateGroup.cs
třídy a nahraďte kód šablony následujícím kódem:
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
public int StudentCount { get; set; }
}
}
Home Úprava kontroleru
Do HomeController.cs
horní části souboru přidejte následující příkazy using:
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.Extensions.Logging;
Přidejte proměnnou třídy pro kontext databáze bezprostředně za levou složenou závorku třídy a získejte instanci kontextu z ASP.NET Core DI:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly SchoolContext _context;
public HomeController(ILogger<HomeController> logger, SchoolContext context)
{
_logger = logger;
_context = context;
}
Přidejte metodu About
s následujícím kódem:
public async Task<ActionResult> About()
{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
return View(await data.AsNoTracking().ToListAsync());
}
Příkaz LINQ seskupí entity studentů podle data registrace, vypočítá počet entit v každé skupině a uloží výsledky do kolekce EnrollmentDateGroup
objektů modelu zobrazení.
Vytvoření zobrazení o aplikaci
Views/Home/About.cshtml
Přidejte soubor s následujícím kódem:
@model IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>
@{
ViewData["Title"] = "Student Body Statistics";
}
<h2>Student Body Statistics</h2>
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>
Spusťte aplikaci a přejděte na stránku O aplikaci. Počet studentů pro každé datum registrace se zobrazí v tabulce.
Získání kódu
Stáhněte nebo zobrazte dokončenou aplikaci.
Další kroky
V tomto kurzu se naučíte:
- Přidání odkazů pro řazení sloupců
- Přidání vyhledávacího pole
- Přidání stránkování do indexu Studentů
- Přidání stránkování do metody Index
- Přidání stránkovacího odkazu
- Vytvoření stránky O aplikaci
V dalším kurzu se dozvíte, jak zpracovávat změny datového modelu pomocí migrací.