Kurz: Zpracování souběžnosti – ASP.NET MVC s EF Core
V předchozích kurzech jste zjistili, jak aktualizovat data. V tomto kurzu se dozvíte, jak řešit konflikty, když více uživatelů aktualizuje stejnou entitu současně.
Vytvoříte webové stránky, které pracují s entitou Department
a zpracovávají chyby souběžnosti. Následující ilustrace ukazují stránky Pro úpravy a odstranění, včetně některých zpráv, které se zobrazují, pokud dojde ke konfliktu souběžnosti.
V tomto kurzu jste:
- Další informace o konfliktech souběžnosti
- Přidání vlastnosti sledování
- Vytvoření kontroleru a zobrazení oddělení
- Aktualizovat zobrazení indexu
- Aktualizace metod úprav
- Aktualizovat zobrazení pro úpravy
- Konflikty souběžnosti testů
- Aktualizace stránky Odstranit
- Aktualizace podrobností a vytváření zobrazení
Požadavky
Konflikty souběžnosti
Konflikt souběžnosti nastane, když jeden uživatel zobrazí data entity, aby je mohl upravit, a pak jiný uživatel aktualizuje data stejné entity před zápisem první změny uživatele do databáze. Pokud nepovolíte detekci takových konfliktů, každý, kdo databázi naposledy přepíše změny ostatních uživatelů. V mnoha aplikacích je toto riziko přijatelné: pokud existuje několik uživatelů nebo několik aktualizací nebo pokud není opravdu důležité, pokud jsou některé změny přepsány, náklady na programování pro souběžnost by mohly výhodu převážit. 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. Říká se tomu pesimistické souběžnost. Například před čtením řádku z databáze si vyžádáte zámek jen pro čtení nebo pro aktualizaci přístupu. Pokud uzamknete řádek pro přístup k aktualizacím, nebudou moct ostatní uživatelé zamknout řádek pro přístup jen pro čtení nebo aktualizaci, protože by získali kopii dat, která se 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 pro správu databází a může způsobit problémy s výkonem při zvýšení počtu uživatelů aplikace. Z těchto důvodů nepodporují všechny systémy správy databází pesimistické souběžnosti. Entity Framework Core neposkytuje žádnou integrovanou podporu a tento kurz vám neukazuje, jak ho implementovat.
Optimistická metoda souběžného zpracování
Alternativou k pesimistické souběžnosti je optimistická souběžnost. Optimistická souběžnost znamená, že umožňuje konflikty souběžnosti proběhnout a pak odpovídajícím způsobem reagovat, pokud ano. Jane například navštíví stránku Upravit oddělení a změní částku rozpočtu pro anglické oddělení z 350 000,00 USD na 0,00 USD.
Před kliknutím na tlačítko Uložit jan navštíví stejnou stránku a změní pole Počáteční datum od 9. 1. 2007 do 9. 1. 2013.
Jane nejprve klikne na Uložit a uvidí její změnu, když se prohlížeč vrátí na stránku indexu.
Pak Jan klikne na uložit na stránce pro úpravy, která stále zobrazuje rozpočet 350 000,00 USD. Co se stane dál, je určeno tím, jak zpracováváte konflikty souběžnosti.
Mezi tyto možnosti patří:
Můžete sledovat, kterou vlastnost uživatel změnil, 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 Jane i Johna – počáteční datum 9. 1. 2013 a rozpočet nulových dolarů. Tato metoda aktualizace může snížit počet konfliktů, které by mohly vést ke ztrátě dat, ale nemůže se vyhnout ztrátě dat, pokud jsou konkurenční změny provedeny ve stejné vlastnosti entity. To, jestli entity Framework funguje tímto způsobem, závisí na tom, jak implementujete kód aktualizace. Často to není praktické ve webové aplikaci, protože může vyžadovat, abyste zachovali velké množství stavu, abyste měli přehled o všech původních hodnotách vlastností pro entitu i nové hodnoty. Udržování velkého množství stavu může ovlivnit výkon aplikace, protože buď vyžaduje prostředky serveru, nebo musí být součástí samotné webové stránky (například ve skrytých polích) nebo v cookiesouboru .
Janovi můžete změnit přepsání janovy změny.
Při příštím procházení anglického oddělení uvidí 9. 1. 2013 a obnovenou hodnotu 350 000,00 USD. Označuje se jako 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 neprovedou žádné kódování pro zpracování souběžnosti, dojde k tomu automaticky.
V databázi můžete zabránit aktualizaci změny Johna.
Obvykle byste zobrazili chybovou zprávu, zobrazili by mu aktuální stav dat a umožnili mu znovu použít změny, pokud je přesto chce udělat. Říká se tomu scénář wins ve Storu . (Hodnoty úložiště dat mají přednost před hodnotami odeslanými klientem.) Scénář Wins pro Store implementujete v tomto kurzu. Tato metoda zajišťuje, aby se nepřepsaly žádné změny, aniž by se 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 DbConcurrencyException
výjimek, které entity Framework vyvolá. Aby bylo možné zjistit, kdy tyto výjimky vyvolat, musí být Entity Framework schopen rozpoznat konflikty. Proto je nutné databázi a datový model odpovídajícím způsobem nakonfigurovat. Mezi možnosti povolení detekce konfliktů patří následující:
V tabulce databáze uveďte sloupec sledování, který lze použít k určení, kdy byl řádek změněn. Pak můžete nakonfigurovat Entity Framework tak, aby zahrnoval tento sloupec do klauzule Where příkazů SQL Update nebo Delete.
Datový typ sloupce sledování je obvykle
rowversion
. Hodnotarowversion
je pořadové číslo, které se při každé aktualizaci řádku zvýší. V příkazu Update nebo Delete klauzule Where obsahuje původní hodnotu sloupce sledování (původní verze řádku). Pokud se aktualizace řádku změnila jiným uživatelem, hodnota verowversion
sloupci se liší od původní hodnoty, takže příkaz Update nebo Delete nemůže řádek aktualizovat kvůli klauzuli Where. Když Entity Framework zjistí, že příkaz Update nebo Delete neaktualizoval žádné řádky (to znamená, že počet ovlivněných řádků je nula), interpretuje to jako konflikt souběžnosti.Nakonfigurujte Entity Framework tak, aby obsahovala původní hodnoty každého sloupce v tabulce v klauzuli Where příkazů Update a Delete.
Stejně jako v první možnosti, pokud se od prvního čtení řádku něco v řádku změnilo, klauzule Where nevrátí řádek pro aktualizaci, který Entity Framework interpretuje jako konflikt souběžnosti. U databázových tabulek, které mají mnoho sloupců, může tento přístup vést k velmi velkým klauzulem Where a může vyžadovat, abyste zachovali velké objemy stavu. Jak bylo uvedeno výše, udržování velkých objemů 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, musíte 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 SQL Where příkazů Update a Delete.
Ve zbývající části tohoto kurzu přidáte rowversion
vlastnost sledování do entity Oddělení, vytvoříte kontroler a zobrazení a otestujete, jestli všechno funguje správně.
Přidání vlastnosti sledování
V Models/Department.cs
souboru přidejte vlastnost sledování s názvem RowVersion:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
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; }
public int? InstructorID { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
public Instructor Administrator { get; set; }
public ICollection<Course> Courses { get; set; }
}
}
Atribut Timestamp
určuje, že tento sloupec bude zahrnut do klauzule Where příkazu Update a Delete odeslané do databáze. Atribut se voláTimestamp
, protože předchozí verze SQL Server použily datový typ SQL timestamp
před nahrazením SQLrowversion
. Typ .NET je rowversion
bajtové pole.
Pokud dáváte přednost používání fluent api, můžete použít metodu IsConcurrencyToken
(in Data/SchoolContext.cs
) k určení vlastnosti 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 potřebujete provést další migraci.
Uložte změny a sestavte projekt a potom do příkazového okna zadejte následující příkazy:
dotnet ef migrations add RowVersion
dotnet ef database update
Vytvoření kontroleru a zobrazení oddělení
Vygenerování kontroleru a zobrazení oddělení, jak jste to udělali dříve pro studenty, kurzy a instruktory.
DepartmentsController.cs
V souboru změňte všechny čtyři výskyty "FirstMidName" na "FullName" tak, aby rozevírací seznamy správce oddělení obsahovaly celé jméno instruktora, nikoli jenom příjmení.
ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID", "FullName", department.InstructorID);
Aktualizovat zobrazení indexu
Modul generování uživatelského rozhraní vytvořil RowVersion
sloupec v zobrazení Index, ale toto pole by se nemělo zobrazit.
Nahraďte kód v souboru Views/Departments/Index.cshtml
následujícím kódem.
@model IEnumerable<ContosoUniversity.Models.Department>
@{
ViewData["Title"] = "Departments";
}
<h2>Departments</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Budget)
</th>
<th>
@Html.DisplayNameFor(model => model.StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Administrator)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Budget)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Administrator.FullName)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.DepartmentID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.DepartmentID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.DepartmentID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Tím se záhlaví změní na Oddělení, odstraní RowVersion
sloupec a místo křestního jména správce se zobrazí celé jméno.
Aktualizace metod úprav
Do metody HttpGet Edit
i Details
metody přidejte AsNoTracking
. V metodě HttpGet Edit
přidejte dychtivé načítání správce.
var department = await _context.Departments
.Include(i => i.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);
Nahraďte existující kód pro metodu HttpPost Edit
následujícím kódem:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, byte[] rowVersion)
{
if (id == null)
{
return NotFound();
}
var departmentToUpdate = await _context.Departments.Include(i => i.Administrator).FirstOrDefaultAsync(m => m.DepartmentID == id);
if (departmentToUpdate == null)
{
Department deletedDepartment = new Department();
await TryUpdateModelAsync(deletedDepartment);
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by another user.");
ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID", "FullName", deletedDepartment.InstructorID);
return View(deletedDepartment);
}
_context.Entry(departmentToUpdate).Property("RowVersion").OriginalValue = rowVersion;
if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.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: {databaseValues.Budget:c}");
}
if (databaseValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("StartDate", $"Current value: {databaseValues.StartDate:d}");
}
if (databaseValues.InstructorID != clientValues.InstructorID)
{
Instructor databaseInstructor = await _context.Instructors.FirstOrDefaultAsync(i => i.ID == databaseValues.InstructorID);
ModelState.AddModelError("InstructorID", $"Current value: {databaseInstructor?.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 = (byte[])databaseValues.RowVersion;
ModelState.Remove("RowVersion");
}
}
}
ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID", "FullName", departmentToUpdate.InstructorID);
return View(departmentToUpdate);
}
Kód začíná pokusem o přečtení oddělení, které se má aktualizovat. FirstOrDefaultAsync
Pokud metoda vrátí hodnotu null, oddělení bylo odstraněno jiným uživatelem. V takovém případě kód používá publikované hodnoty formuláře k vytvoření Department
entity, aby se stránka Upravit znovu zobrazila s chybovou zprávou. Alternativně byste entitu Department
nemuseli znovu vytvořit, pokud se zobrazí jenom chybová zpráva bez opětovného zobrazení polí oddělení.
Zobrazení ukládá původní RowVersion
hodnotu do skrytého pole a tato metoda obdrží tuto hodnotu v parametru rowVersion
. Před voláním SaveChanges
musíte tuto původní RowVersion
hodnotu vlastnosti vložit do OriginalValues
kolekce pro entitu.
_context.Entry(departmentToUpdate).Property("RowVersion").OriginalValue = rowVersion;
Když pak Entity Framework vytvoří příkaz SQL UPDATE, tento příkaz bude obsahovat klauzuli WHERE, která hledá řádek, který má původní RowVersion
hodnotu. Pokud příkaz UPDATE neovlivní žádné řádky (žádné řádky nemají původní RowVersion
hodnotu), entity Framework vyvolá DbUpdateConcurrencyException
výjimku.
Kód v bloku catch pro danou výjimku získá ovlivněnou entitu Oddělení, která má aktualizované hodnoty z Entries
vlastnosti objektu výjimky.
var exceptionEntry = ex.Entries.Single();
Kolekce Entries
bude mít pouze jeden EntityEntry
objekt. Tento objekt můžete použít k získání nových hodnot zadaných uživatelem a aktuálními hodnotami databáze.
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
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 (pro stručnost se zobrazí jenom jedno pole).
var databaseValues = (Department)databaseEntry.ToObject();
if (databaseValues.Name != clientValues.Name)
{
ModelState.AddModelError("Name", $"Current value: {databaseValues.Name}");
Nakonec kód nastaví RowVersion
hodnotu departmentToUpdate
na novou hodnotu načtenou z databáze. Tato nová RowVersion
hodnota bude uložena ve skrytém poli při opětovném zobrazení stránky Upravit a při příštím kliknutí uživatele na Uložit se zobrazí pouze chyby souběžnosti, ke kterým dochází, protože se zachytí opětovné zobrazení stránky Pro úpravy.
departmentToUpdate.RowVersion = (byte[])databaseValues.RowVersion;
ModelState.Remove("RowVersion");
Příkaz ModelState.Remove
je povinný, protože ModelState
má starou RowVersion
hodnotu. V zobrazení ModelState
má hodnota pole přednost před hodnotami vlastností modelu, pokud jsou k dispozici oba.
Aktualizovat zobrazení pro úpravy
V souboru Views/Departments/Edit.cshtml
proveďte následující změny:
Přidejte skryté pole pro uložení
RowVersion
hodnoty vlastnosti bezprostředně za skrytým polemDepartmentID
vlastnosti.Do rozevíracího seznamu přidejte možnost Vybrat správce.
@model ContosoUniversity.Models.Department
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="DepartmentID" />
<input type="hidden" asp-for="RowVersion" />
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Budget" class="control-label"></label>
<input asp-for="Budget" class="form-control" />
<span asp-validation-for="Budget" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="InstructorID" class="control-label"></label>
<select asp-for="InstructorID" class="form-control" asp-items="ViewBag.InstructorID">
<option value="">-- Select Administrator --</option>
</select>
<span asp-validation-for="InstructorID" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Konflikty souběžnosti testů
Spusťte aplikaci a přejděte na stránku Index 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 hypertextový odkaz Upravit pro anglické oddělení. Na dvou kartách prohlížeče se teď zobrazují stejné informace.
Změňte pole na první kartě prohlížeče a klikněte na Uložit.
Prohlížeč zobrazuje stránku Index se změněnou hodnotou.
Změňte pole na druhé kartě prohlížeče.
Klikněte na Uložit. Zobrazí se chybová zpráva:
Znovu klikněte na Uložit . Hodnota zadaná na druhé kartě prohlížeče se uloží. Uložené hodnoty se zobrazí, když se zobrazí stránka Index.
Aktualizace stránky Odstranit
Na stránce 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 dostupná pro metodu HttpPost Delete
, která se volá, když uživatel potvrdí odstranění. Když Entity Framework vytvoří příkaz SQL DELETE, obsahuje klauzuli WHERE s původní RowVersion
hodnotou. Pokud má příkaz vliv na nula řádků (to znamená, že řádek byl změněn po zobrazení stránky potvrzení odstranění), vyvolá se výjimka souběžnosti a metoda HttpGet Delete
se volá s příznakem chyby nastaveným na true, aby se znovu zobrazila potvrzovací stránka 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 nezobrazí žádná chybová zpráva.
Aktualizace metod delete v kontroleru oddělení
Nahraďte DepartmentsController.cs
metodu HttpGet Delete
následujícím kódem:
public async Task<IActionResult> Delete(int? id, bool? concurrencyError)
{
if (id == null)
{
return NotFound();
}
var department = await _context.Departments
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);
if (department == null)
{
if (concurrencyError.GetValueOrDefault())
{
return RedirectToAction(nameof(Index));
}
return NotFound();
}
if (concurrencyError.GetValueOrDefault())
{
ViewData["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 tento příznak pravdivý a zadané oddělení již neexistuje, byl odstraněn jiným uživatelem. V takovém případě se kód přesměruje na stránku indexu. Pokud je tento příznak pravdivý a oddělení existuje, změnil ho jiný uživatel. V takovém případě kód odešle do zobrazení chybovou zprávu pomocí ViewData
.
Nahraďte kód v metodě HttpPost Delete
(pojmenované DeleteConfirmed
) následujícím kódem:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(Department department)
{
try
{
if (await _context.Departments.AnyAsync(m => m.DepartmentID == department.DepartmentID))
{
_context.Departments.Remove(department);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Index));
}
catch (DbUpdateConcurrencyException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { concurrencyError = true, id = department.DepartmentID });
}
}
V vygenerovaný kód, který jste právě nahradili, tato metoda akceptovala pouze ID záznamu:
public async Task<IActionResult> 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 EF k hodnotě vlastnosti RowVers kromě klíče záznamu.
public async Task<IActionResult> Delete(Department department)
Změnili jste také název metody akce z DeleteConfirmed
na Delete
. Vygenerovaný kód použil název DeleteConfirmed
k poskytnutí jedinečného podpisu metody HttpPost. (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 metody HttpPost a HttpGet delete.
Pokud je oddělení již odstraněno, AnyAsync
metoda vrátí hodnotu false a aplikace se jednoduše vrátí do metody Index.
Pokud dojde k chybě souběžnosti, kód znovu zobrazí stránku potvrzení odstranění a zobrazí příznak, který označuje, že by se měla zobrazit chybová zpráva souběžnosti.
Aktualizace zobrazení Pro odstranění
Nahraďte Views/Departments/Delete.cshtml
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
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<p class="text-danger">@ViewData["ConcurrencyErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Department</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Budget)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Administrator)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
</dl>
<form asp-action="Delete">
<input type="hidden" asp-for="DepartmentID" />
<input type="hidden" asp-for="RowVersion" />
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>
To provede následující změny:
Přidá mezi nadpisy chybovou
h2
h3
zprávu.Nahradí Jméno FirstMidName úplným jménem v poli Správce .
Odebere pole RowVersion.
Přidá skryté pole vlastnosti
RowVersion
.
Spusťte aplikaci a přejděte na stránku Index oddělení. Klikněte pravým tlačítkem myši na hypertextový odkaz Odstranit pro anglické oddělení a vyberte Otevřít v nové kartě a pak 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:
Na druhé kartě klikněte na Odstranit. Zobrazí se chybová zpráva souběžnosti a hodnoty oddělení se aktualizují s informacemi o tom, co je 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.
Aktualizace podrobností a vytváření zobrazení
Volitelně můžete vyčistit vygenerovaný kód v zobrazení Podrobnosti a Vytvořit.
Nahraďte kód pro Views/Departments/Details.cshtml
odstranění sloupce RowVersion a zobrazte úplný název správce.
@model ContosoUniversity.Models.Department
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Department</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Budget)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Administrator)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.DepartmentID">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
Nahraďte kód Views/Departments/Create.cshtml
pro přidání možnosti Vybrat do rozevíracího seznamu.
@model ContosoUniversity.Models.Department
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Budget" class="control-label"></label>
<input asp-for="Budget" class="form-control" />
<span asp-validation-for="Budget" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="InstructorID" class="control-label"></label>
<select asp-for="InstructorID" class="form-control" asp-items="ViewBag.InstructorID">
<option value="">-- Select Administrator --</option>
</select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Získání kódu
Stáhněte nebo zobrazte dokončenou aplikaci.
Další materiály
Další informace o zpracování souběžnosti v EF Core najdete v tématu Konflikty souběžnosti.
Další kroky
V tomto kurzu jste:
- Informace o konfliktech souběžnosti
- Přidání vlastnosti sledování
- Vytvořený kontroler oddělení a zobrazení
- Aktualizované zobrazení indexu
- Aktualizované metody úprav
- Aktualizované zobrazení pro úpravy
- Testované konflikty souběžnosti
- Aktualizace stránky Odstranit
- Aktualizované podrobnosti a vytváření zobrazení
V dalším kurzu se dozvíte, jak implementovat dědičnost tabulek na hierarchii pro entity instruktora a studenta.