Kurz: Zpracování souběžnosti pomocí EF v aplikaci ASP.NET MVC 5
V předchozích kurzech jste se naučili aktualizovat data. V tomto kurzu se dozvíte, jak pomocí optimistické souběžnosti zpracovat konflikty, když více uživatelů aktualizuje stejnou entitu současně. Webové stránky, které pracují s entitou Department
, změníte tak, aby zpracovávaly chyby souběžnosti. Následující ilustrace znázorňují stránky Upravit a odstranit, včetně některých zpráv, které se zobrazí, pokud dojde ke konfliktu souběžnosti.
V tomto kurzu se naučíte:
- Další informace o konfliktech souběžnosti
- Přidání optimistické souběžnosti
- Úprava kontroleru oddělení
- Zpracování souběžnosti testů
- Aktualizace stránky Odstranit
Požadavky
Konflikty souběžnosti
Ke konfliktu souběžnosti dojde, když jeden uživatel zobrazí data entity, aby je mohl upravit, a pak jiný uživatel aktualizuje data stejné entity před zápisem změny prvního uživatele do databáze. Pokud nepovolíte detekci takových konfliktů, každý, kdo aktualizuje databázi, naposledy přepíše změny jiného uživatele. V mnoha aplikacích je toto riziko přijatelné: pokud existuje několik uživatelů nebo několik aktualizací, nebo pokud není ve skutečnosti důležité, pokud jsou některé změny přepsány, náklady na programování pro souběžnost můžou výhodu převažovat. V takovém případě nemusíte aplikaci konfigurovat tak, aby zpracovávala konflikty souběžnosti.
Pesimistické souběžnosti (uzamykání)
Pokud vaše aplikace potřebuje zabránit náhodné ztrátě dat ve scénářích souběžnosti, jedním ze způsobů, jak to udělat, je použít zámky databáze. Tomu se říká pesimistické souběžnost. Například před čtením řádku z databáze si vyžádáte zámek jen pro čtení nebo pro přístup k aktualizaci. Pokud uzamknete řádek pro přístup k aktualizacím, nebudou moct ostatní uživatelé zamknout řádek pro přístup jen pro čtení nebo pro aktualizaci, protože by získali kopii dat, která se právě mění. Pokud uzamknete řádek pro přístup jen pro čtení, ostatní ho můžou také uzamknout pro přístup jen pro čtení, ale ne pro aktualizaci.
Správa zámků má nevýhody. Program může být složitý. Vyžaduje významné prostředky správy databází a může způsobit problémy s výkonem při nárůstu počtu uživatelů aplikace. Z těchto důvodů ne všechny systémy pro správu databází podporují pesimistické souběžnosti. Entity Framework neposkytuje žádnou integrovanou podporu a tento kurz vám neukazuje, jak ji implementovat.
Optimistická metoda souběžného zpracování
Alternativou k pesimistické souběžnosti je optimistická souběžnost. Optimistická souběžnost znamená, že umožňuje, aby došlo ke konfliktům souběžnosti, a pak odpovídajícím způsobem reagovat, pokud ano. Jan například spustí stránku Úpravy oddělení, změní částku rozpočtu pro anglické oddělení z 350 000,00 USD na 0,00 USD.
Než Jan klikne na Uložit, jane spustí stejnou stránku a změní pole Počáteční datum od 1. 9. 2007 do 8. 8. 2013.
Jan nejprve klikne na Uložit a uvidí změnu, když se prohlížeč vrátí na stránku Index a pak Jane klikne na Uložit. Co se stane dál, určuje způsob zpracování konfliktů souběžnosti. Mezi tyto možnosti patří:
Můžete sledovat, kterou vlastnost uživatel upravil, a aktualizovat pouze odpovídající sloupce v databázi. V ukázkovém scénáři by se neztratila žádná data, protože dva uživatelé aktualizovali různé vlastnosti. Když někdo příště přejde do anglického oddělení, uvidí změny Johna i Janeho – počáteční datum 8. 8. 2013 a rozpočet nula dolarů.
Tato metoda aktualizace může snížit počet konfliktů, které můžou vést ke ztrátě dat, ale nemůže se vyhnout ztrátě dat, pokud jsou u stejné vlastnosti entity provedeny konkurenční změny. To, jestli Entity Framework funguje tímto způsobem, závisí na tom, jak implementujete aktualizační kód. Často to není praktické ve webové aplikaci, protože může vyžadovat, abyste zachovali velké množství stavu, abyste mohli sledovat všechny původní hodnoty vlastností pro entitu i nové hodnoty. Udržování velkého množství stavu může ovlivnit výkon aplikace, protože vyžaduje prostředky serveru nebo musí být součástí samotné webové stránky (například ve skrytých polích) nebo v souboru cookie.
Janovu změnu můžete nechat přepsat. Když někdo příště přejde do anglického oddělení, uvidí 8.8.2013 a obnovenou hodnotu 350 000,00 USD. To se nazývá klient wins nebo last ve scénáři Wins . (Všechny hodnoty z klienta mají přednost před tím, co je v úložišti dat.) Jak je uvedeno v úvodu do této části, pokud neproděláte žádné kódování pro zpracování souběžnosti, dojde k tomu automaticky.
V databázi můžete zabránit aktualizaci janovy změny. Obvykle se zobrazí chybová zpráva, zobrazí se její aktuální stav dat a umožníte jí znovu použít její změny, pokud je stále chce provést. Tomu se říká scénář wins ve Storu. (Hodnoty úložiště dat mají přednost před hodnotami odeslanými klientem.) V tomto kurzu implementujete scénář Wins pro Store. Tato metoda zajišťuje, aby se nepřepsaly žádné změny, aniž by uživatel upozorňoval na to, co se děje.
Zjišťování konfliktů souběžnosti
Konflikty můžete vyřešit zpracováním výjimek OptimisticConcurrencyException , které entity Framework vyvolává. Aby bylo možné zjistit, kdy tyto výjimky vyvolat, musí být Entity Framework schopen detekovat konflikty. Proto je nutné správně nakonfigurovat databázi a datový model. Mezi možnosti povolení detekce konfliktů patří:
V tabulce databáze zahrňte sledovací sloupec, který lze použít k určení, kdy byl řádek změněn. Potom můžete rozhraní Entity Framework nakonfigurovat tak, aby zahrnovalo tento sloupec v
Where
klauzuli SQLUpdate
neboDelete
příkazů.Datový typ sloupce sledování je obvykle rowversion. Hodnota rowversion je pořadové číslo, které se při každé aktualizaci řádku zvýší.
Update
V klauzuli neboDelete
příkazWhere
obsahuje původní hodnotu sloupce sledování (původní verze řádku). Pokud byl řádek aktualizovaný jiným uživatelem změněn, hodnota verowversion
sloupci se liší od původní hodnoty, takže příkaz nemůžeDelete
najít řádek, kterýUpdate
se má aktualizovat kvůliWhere
klauzuli. Když Entity Framework zjistí, že žádné řádky nebyly aktualizovány příkazem neboDelete
příkazemUpdate
(to znamená, že počet ovlivněných řádků je nula), interpretuje to jako konflikt souběžnosti.Nakonfigurujte Entity Framework tak, aby zahrnovala původní hodnoty každého sloupce v tabulce v
Where
klauzuliUpdate
aDelete
příkazy.Stejně jako v první možnosti, pokud se od prvního čtení řádku něco v řádku změnilo,
Where
klauzule nevrátí řádek pro aktualizaci, který Entity Framework interpretuje jako konflikt souběžnosti. U databázových tabulek s mnoha sloupci může tento přístup vést k velmi velkýmWhere
klauzulemi a může vyžadovat, abyste zachovali velké množství stavu. Jak jsme uvedli dříve, udržování velkého objemu stavu může ovlivnit výkon aplikace. Proto se tento přístup obecně nedoporučuje a není to metoda použitá v tomto kurzu.Pokud chcete tento přístup implementovat ke souběžnosti, je nutné označit všechny vlastnosti jiného než primárního klíče v entitě, pro kterou chcete sledovat souběžnost, přidáním atributu ConcurrencyCheck do nich. Tato změna umožňuje rozhraní Entity Framework zahrnout všechny sloupce do klauzule
UPDATE
SQLWHERE
příkazů.
Ve zbývající části tohoto kurzu přidáte do Department
entity vlastnost sledování rowversion, vytvoříte kontroler a zobrazení a otestujete, jestli všechno funguje správně.
Přidání optimistické souběžnosti
Do pole Models\Department.cs přidejte vlastnost sledování s názvem RowVersion
:
public class Department
{
public int DepartmentID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
[Display(Name = "Administrator")]
public int? InstructorID { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
public virtual Instructor Administrator { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
Atribut časového razítka určuje, že tento sloupec bude zahrnut do Where
klauzule Update
a Delete
příkazů odesílaných do databáze. Atribut se nazývá Časové razítko, protože předchozí verze SQL Serveru používaly datový typ časového razítka SQL před nahrazením verze řádku SQL. Typ .Net pro rowversion je bajtové pole.
Pokud raději používáte rozhraní FLUENT API, můžete pomocí metody IsConcurrencyToken určit vlastnost sledování, jak je znázorněno v následujícím příkladu:
modelBuilder.Entity<Department>()
.Property(p => p.RowVersion).IsConcurrencyToken();
Přidáním vlastnosti, kterou jste změnili model databáze, takže je potřeba provést další migraci. V konzole Správce balíčků (PMC) zadejte následující příkazy:
Add-Migration RowVersion
Update-Database
Úprava kontroleru oddělení
Do pole Controllers\DepartmentController.cs přidejte using
příkaz:
using System.Data.Entity.Infrastructure;
V souboru DepartmentController.cs změňte všechny čtyři výskyty "Příjmení" na "FullName", aby rozevírací seznamy správců oddělení obsahovaly celé jméno instruktora, a ne jenom příjmení.
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName");
Nahraďte stávající kód pro metodu HttpPost
Edit
následujícím kódem:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(int? id, byte[] rowVersion)
{
string[] fieldsToBind = new string[] { "Name", "Budget", "StartDate", "InstructorID", "RowVersion" };
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var departmentToUpdate = await db.Departments.FindAsync(id);
if (departmentToUpdate == null)
{
Department deletedDepartment = new Department();
TryUpdateModel(deletedDepartment, fieldsToBind);
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by another user.");
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", deletedDepartment.InstructorID);
return View(deletedDepartment);
}
if (TryUpdateModel(departmentToUpdate, fieldsToBind))
{
try
{
db.Entry(departmentToUpdate).OriginalValues["RowVersion"] = rowVersion;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var clientValues = (Department)entry.Entity;
var databaseEntry = entry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by another user.");
}
else
{
var databaseValues = (Department)databaseEntry.ToObject();
if (databaseValues.Name != clientValues.Name)
ModelState.AddModelError("Name", "Current value: "
+ databaseValues.Name);
if (databaseValues.Budget != clientValues.Budget)
ModelState.AddModelError("Budget", "Current value: "
+ String.Format("{0:c}", databaseValues.Budget));
if (databaseValues.StartDate != clientValues.StartDate)
ModelState.AddModelError("StartDate", "Current value: "
+ String.Format("{0:d}", databaseValues.StartDate));
if (databaseValues.InstructorID != clientValues.InstructorID)
ModelState.AddModelError("InstructorID", "Current value: "
+ db.Instructors.Find(databaseValues.InstructorID).FullName);
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
departmentToUpdate.RowVersion = databaseValues.RowVersion;
}
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", departmentToUpdate.InstructorID);
return View(departmentToUpdate);
}
FindAsync
Pokud metoda vrátí hodnotu null, oddělení bylo odstraněno jiným uživatelem. Zobrazený kód používá hodnoty publikovaného formuláře k vytvoření entity oddělení, aby se stránka Upravit znovu zobrazila s chybovou zprávou. Jako alternativu byste nemuseli entitu oddělení znovu vytvořit, pokud se zobrazí jenom chybová zpráva bez opětovného zobrazení polí oddělení.
Zobrazení uloží původní RowVersion
hodnotu do skrytého pole a metoda ji obdrží v parametru rowVersion
. Než zavoláte SaveChanges
, musíte tuto původní RowVersion
hodnotu vlastnosti vložit do OriginalValues
kolekce pro entitu. Když pak Entity Framework vytvoří příkaz SQL UPDATE
, bude tento příkaz obsahovat WHERE
klauzuli, která hledá řádek s původní RowVersion
hodnotou.
Pokud příkaz UPDATE
neovlivní žádné řádky (žádné řádky nemají původní RowVersion
hodnotu), Entity Framework vyvolá DbUpdateConcurrencyException
výjimku a kód v catch
bloku získá ovlivněnou Department
entitu z objektu výjimky.
var entry = ex.Entries.Single();
Tento objekt má nové hodnoty zadané uživatelem ve své Entity
vlastnosti a můžete získat hodnoty načtené z databáze voláním GetDatabaseValues
metody.
var clientValues = (Department)entry.Entity;
var databaseEntry = entry.GetDatabaseValues();
Metoda GetDatabaseValues
vrátí hodnotu null, pokud někdo odstranil řádek z databáze; jinak je nutné přetypovat vrácený objekt do Department
třídy, aby bylo možné získat přístup k vlastnostem Department
. (Vzhledem k tomu, že jste již zkontrolovali odstranění, bude mít hodnotu null pouze v případě, databaseEntry
že oddělení bylo odstraněno po FindAsync
provedení a před SaveChanges
provedením.)
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by another user.");
}
else
{
var databaseValues = (Department)databaseEntry.ToObject();
Dále kód přidá vlastní chybovou zprávu pro každý sloupec s hodnotami databáze, které se liší od toho, co uživatel zadal na stránce Upravit:
if (databaseValues.Name != currentValues.Name)
ModelState.AddModelError("Name", "Current value: " + databaseValues.Name);
// ...
Delší chybová zpráva vysvětluje, co se stalo a co s tím dělat:
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The"
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
Nakonec kód nastaví RowVersion
hodnotu objektu Department
na novou hodnotu načtenou z databáze. Tato nová RowVersion
hodnota se uloží do skrytého pole při opětovném zobrazení stránky Pro úpravy a při příštím kliknutí uživatele na Uložit dojde pouze k chybám souběžnosti, ke kterým dochází, protože se při přehrání stránky Pro úpravy zachytí.
V Views\Department\Edit.cshtml přidejte skryté pole pro uložení RowVersion
hodnoty vlastnosti bezprostředně za skryté pole vlastnosti DepartmentID
:
@model ContosoUniversity.Models.Department
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Department</h4>
<hr />
@Html.ValidationSummary(true)
@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)
Zpracování souběžnosti testů
Spusťte web a klikněte na Oddělení.
Klikněte pravým tlačítkem myši na hypertextový odkaz Pro anglické oddělení a vyberte Otevřít v nové kartě a potom klikněte na upravit hypertextový odkaz pro anglické oddělení. Dvě karty zobrazují stejné informace.
Změňte pole na první kartě prohlížeče a klikněte na Uložit.
V prohlížeči se zobrazí stránka Index se změněnou hodnotou.
Změňte pole na druhé kartě prohlížeče a klikněte na Uložit. Zobrazí se chybová zpráva:
Znovu klikněte na Uložit . Hodnota, kterou jste zadali na druhé kartě prohlížeče, se uloží spolu s původní hodnotou dat, která jste změnili v prvním prohlížeči. Uložené hodnoty se zobrazí, když se zobrazí stránka Index.
Aktualizace stránky Odstranit
U stránky Odstranit služba Entity Framework detekuje konflikty souběžnosti způsobené jiným uživatelem, který upravuje oddělení podobným způsobem. Když metoda HttpGet
Delete
zobrazí potvrzovací zobrazení, zobrazení obsahuje původní RowVersion
hodnotu ve skrytém poli. Tato hodnota je pak k dispozici metodě HttpPost
Delete
, která se volá, když uživatel potvrdí odstranění. Když Entity Framework vytvoří příkaz SQL DELETE
, obsahuje WHERE
klauzuli s původní RowVersion
hodnotou. Pokud má příkaz za následek nulový dopad na řádky (to znamená, že se řádek po zobrazení stránky potvrzení odstranění změnil), vyvolá se výjimka souběžnosti a HttpGet Delete
volá se metoda s příznakem chyby nastaveným na true
opětovné zobrazení potvrzovací stránky s chybovou zprávou. Je také možné, že byly ovlivněny nulové řádky, protože řádek odstranil jiný uživatel, takže v takovém případě se zobrazí jiná chybová zpráva.
V DepartmentController.cs nahraďte metodu HttpGet
Delete
následujícím kódem:
public async Task<ActionResult> Delete(int? id, bool? concurrencyError)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Department department = await db.Departments.FindAsync(id);
if (department == null)
{
if (concurrencyError.GetValueOrDefault())
{
return RedirectToAction("Index");
}
return HttpNotFound();
}
if (concurrencyError.GetValueOrDefault())
{
ViewBag.ConcurrencyErrorMessage = "The record you attempted to delete "
+ "was modified by another user after you got the original values. "
+ "The delete operation was canceled and the current values in the "
+ "database have been displayed. If you still want to delete this "
+ "record, click the Delete button again. Otherwise "
+ "click the Back to List hyperlink.";
}
return View(department);
}
Metoda přijímá volitelný parametr, který označuje, zda se stránka znovu zobrazuje po chybě souběžnosti. Pokud je true
tento příznak , chybová zpráva se odešle do zobrazení pomocí ViewBag
vlastnosti.
Nahraďte kód v HttpPost
Delete
metodě (pojmenovaný DeleteConfirmed
) následujícím kódem:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Delete(Department department)
{
try
{
db.Entry(department).State = EntityState.Deleted;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToAction("Delete", new { concurrencyError = true, id=department.DepartmentID });
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException and add a line here to write a log.
ModelState.AddModelError(string.Empty, "Unable to delete. Try again, and if the problem persists contact your system administrator.");
return View(department);
}
}
V vygenerovaný kód, který jste právě nahradili, tato metoda přijala pouze ID záznamu:
public async Task<ActionResult> DeleteConfirmed(int id)
Tento parametr jste změnili na Department
instanci entity vytvořenou pořadačem modelu. Tím získáte přístup k hodnotě RowVersion
vlastnosti kromě klíče záznamu.
public async Task<ActionResult> Delete(Department department)
Změnili jste také název metody akce z DeleteConfirmed
na Delete
. Vygenerovaný kód pojmenovaný metodou HttpPost
Delete
DeleteConfirmed
HttpPost
, který metodě poskytne jedinečný podpis. ( CLR vyžaduje přetížené metody, aby měly různé parametry metody.) Teď, když jsou podpisy jedinečné, můžete držet konvence MVC a použít stejný název pro HttpPost
metody a HttpGet
metody delete.
Pokud dojde k chybě souběžnosti, kód znovu zobrazí potvrzovací stránku odstranění a zobrazí příznak, který označuje, že by se měla zobrazit chybová zpráva souběžnosti.
V Zobrazení\Department\Delete.cshtml nahraďte vygenerovaný kód následujícím kódem, který přidá pole chybové zprávy a skrytá pole pro vlastnosti DepartmentID a RowVersion. Změny jsou zvýrazněné.
@model ContosoUniversity.Models.Department
@{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<p class="error">@ViewBag.ConcurrencyErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Department</h4>
<hr />
<dl class="dl-horizontal">
<dt>
Administrator
</dt>
<dd>
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd>
@Html.DisplayFor(model => model.Budget)
</dd>
<dt>
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd>
@Html.DisplayFor(model => model.StartDate)
</dd>
</dl>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
@Html.ActionLink("Back to List", "Index")
</div>
}
</div>
Tento kód přidá mezi nadpisy chybovou h2
h3
zprávu:
<p class="error">@ViewBag.ConcurrencyErrorMessage</p>
LastName
Nahradí ho FullName
Administrator
v poli:
<dt>
Administrator
</dt>
<dd>
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
Nakonec za příkaz přidá skrytá pole a DepartmentID
RowVersion
vlastnosti Html.BeginForm
:
@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)
Spusťte stránku Index oddělení. Klikněte pravým tlačítkem myši na odstranit hypertextový odkaz pro anglické oddělení a vyberte Otevřít v nové kartě a potom na první kartě klikněte na hypertextový odkaz Upravit pro anglické oddělení.
V prvním okně změňte jednu z hodnot a klikněte na Uložit.
Stránka Index potvrdí změnu.
Na druhé kartě klikněte na Odstranit.
Zobrazí se chybová zpráva o souběžnosti a hodnoty oddělení se aktualizují s informacemi, které jsou aktuálně v databázi.
Pokud znovu kliknete na Odstranit , budete přesměrováni na stránku Index, která ukazuje, že oddělení bylo odstraněno.
Získání kódu
Další materiály
Odkazy na další prostředky Entity Framework najdete v ASP.NET přístupu k datům – doporučené zdroje informací.
Informace o dalších způsobech zpracování různých scénářů souběžnosti naleznete v tématu Optimistic Concurrency Patterns and Working with Property Values na MSDN. V dalším kurzu se dozvíte, jak implementovat dědičnost tabulek podle hierarchie pro entity Instructor
a Student
entity.
Další kroky
V tomto kurzu se naučíte:
- Dozvěděli jste se o konfliktech souběžnosti
- Přidání optimistické souběžnosti
- Upravený kontroler oddělení
- Testování zpracování souběžnosti
- Aktualizace stránky Odstranit
V dalším článku se dozvíte, jak implementovat dědičnost v datovém modelu.