Öğretici: Eşzamanlılığı işleme - ile MVC ASP.NET EF Core
Önceki öğreticilerde verileri güncelleştirmeyi öğrendinsiniz. Bu öğreticide, birden çok kullanıcı aynı varlığı aynı anda güncelleştirdiğinde çakışmaların nasıl işleneceği gösterilmektedir.
Varlıkla Department
çalışan ve eşzamanlılık hatalarını işleyen web sayfaları oluşturacaksınız. Aşağıdaki çizimlerde, eşzamanlılık çakışması oluştuğunda görüntülenen bazı iletiler de dahil olmak üzere Düzenle ve Sil sayfaları gösterilmektedir.
Bu öğreticide şunları yaptınız:
- Eşzamanlılık çakışmaları hakkında bilgi edinin
- İzleme özelliği ekleme
- Departman denetleyicisi ve görünümleri oluşturma
- Dizin görünümünü güncelleştirme
- Düzenleme yöntemlerini güncelleştirme
- Düzenleme görünümünü güncelleştir
- Eşzamanlılık çakışmalarını test edin
- Sil sayfasını güncelleştirme
- Güncelleştirme Ayrıntıları ve Görünüm oluşturma
Önkoşullar
Eşzamanlılık çakışmaları
Eşzamanlılık çakışması, bir kullanıcının düzenlemek için bir varlığın verilerini görüntülemesi ve ardından başka bir kullanıcı, ilk kullanıcının değişikliği veritabanına yazılmadan önce aynı varlığın verilerini güncelleştirdiğinde oluşur. Bu tür çakışmaların algılanması etkinleştirilmezse, veritabanını güncelleştiren kişi son olarak diğer kullanıcının değişikliklerinin üzerine yazar. Birçok uygulamada bu risk kabul edilebilir bir risktir: Az sayıda kullanıcı veya birkaç güncelleştirme varsa veya bazı değişikliklerin üzerine yazılırsa gerçekten kritik değilse eşzamanlılık programlama maliyeti avantajdan daha ağır basabilir. Bu durumda, eşzamanlılık çakışmalarını işlemek için uygulamayı yapılandırmanız gerekmez.
Kötümser eşzamanlılık (kilitleme)
Uygulamanızın eşzamanlılık senaryolarında yanlışlıkla veri kaybını önlemesi gerekiyorsa, bunu gerçekleştirmenin bir yolu veritabanı kilitlerini kullanmaktır. Buna kötümser eşzamanlılık denir. Örneğin, veritabanından bir satırı okumadan önce salt okunur veya güncelleştirme erişimi için bir kilit isteyebilirsiniz. Bir satırı güncelleştirme erişimi için kilitlerseniz, değiştirme sürecinde olan verilerin bir kopyasını alacakları için, başka hiçbir kullanıcının satırı salt okunur veya güncelleştirme erişimi için kilitlemesine izin verilmez. Bir satırı salt okunur erişim için kilitlerseniz, diğerleri de satırı salt okunur erişim için kilitleyebilir ancak güncelleştirme için kilitleyebilir.
Kilitleri yönetmenin dezavantajları vardır. Programlamak karmaşık olabilir. Önemli veritabanı yönetim kaynakları gerektirir ve bir uygulamanın kullanıcı sayısı arttıkça performans sorunlarına neden olabilir. Bu nedenlerden dolayı, tüm veritabanı yönetim sistemleri kötümser eşzamanlılığı desteklemez. Entity Framework Core bunun için yerleşik destek sağlamaz ve bu öğretici bunu nasıl uygulayabileceğinizi göstermez.
İyimser Eşzamanlılık
Kötümser eşzamanlılığın alternatifi iyimser eşzamanlılıktır. İyimser eşzamanlılık, eşzamanlılık çakışmalarının gerçekleşmesine izin vermek ve varsa uygun şekilde tepki vermek anlamına gelir. Örneğin, Jane Bölüm Düzenleme sayfasını ziyaret etti ve İngilizce bölümü için Bütçe tutarını 350.000,00 TL olan 0,00 TL olarak değiştirir.
Jane Kaydet'e tıklamadan önce, John aynı sayfayı ziyaret eder ve Başlangıç Tarihi alanını 1/9/2007'den 1/9/2013'e değiştirir.
Jane önce Kaydet'e tıklar ve tarayıcı Dizin sayfasına döndüğünde değişikliğini görür.
Ardından John, 350.000,00 TL bütçeyi gösteren Düzenleme sayfasında Kaydet'e tıklar. Bundan sonra ne olacağı eşzamanlılık çakışmalarını nasıl işlediğinize göre belirlenir.
Seçeneklerden bazıları şunlardır:
Kullanıcının hangi özelliği değiştirdiğini izleyebilir ve yalnızca veritabanındaki ilgili sütunları güncelleştirebilirsiniz.
Örnek senaryoda, iki kullanıcı tarafından farklı özellikler güncelleştirildiğinden hiçbir veri kaybolmaz. bir daha İngilizce bölümüne göz atan biri, jane'in ve John'un değişikliklerini (1/9/2013 başlangıç tarihi ve sıfır dolar bütçe) görür. Bu güncelleştirme yöntemi, veri kaybına neden olabilecek çakışma sayısını azaltabilir, ancak bir varlığın aynı özelliğinde rakip değişiklikler yapıldığında veri kaybını önleyemez. Entity Framework'ün bu şekilde çalışıp çalışmadığı, güncelleştirme kodunuzu nasıl uyguladığınıza bağlıdır. Bir varlığın tüm özgün özellik değerlerini ve yeni değerleri izlemek için büyük miktarlarda durum korumanızı gerektirebileceğinden, web uygulamasında genellikle pratik değildir. Sunucu kaynakları gerektirdiğinden veya web sayfasının kendisine (örneğin, gizli alanlara) veya bir cookieiçine dahil edilmesi gerektiğinden, büyük miktarda durumun korunması uygulama performansını etkileyebilir.
John'un değişiminin Jane'in değişikliğinin üzerine yazmasına izin vekleyebilirsiniz.
bir daha İngilizce bölümüne göz atılan kişiler 1/9/2013 ve geri yüklenen 350.000,00 ABD doları değerini görür. Buna İstemci Kazanır veya Wins senaryosunda Son denir. (İstemcideki tüm değerler veri deposundakilerden önceliklidir.) Bu bölüme giriş bölümünde belirtildiği gibi, eşzamanlılık işleme için herhangi bir kodlama yapmazsanız, bu otomatik olarak gerçekleşir.
John'un değişikliğinin veritabanında güncelleştirilmesini engelleyebilirsiniz.
Genellikle bir hata iletisi görüntüler, verilerin geçerli durumunu gösterir ve yine de yapmak isterse değişikliklerini yeniden uygulamasına izin verirsiniz. Buna Store Wins senaryosu adı verilir. (Veri deposu değerleri, istemci tarafından gönderilen değerlerden önceliklidir.) Bu öğreticide Store Wins senaryosunu uygulayacaksınız. Bu yöntem, bir kullanıcı olup bitenler konusunda uyarılmadan hiçbir değişikliğin üzerine yazılmamasını sağlar.
Eşzamanlılık çakışmalarını algılama
Entity Framework'ün oluşturduğu özel durumları işleyerek DbConcurrencyException
çakışmaları çözebilirsiniz. Bu özel durumların ne zaman oluşturulacağı hakkında bilgi edinmek için Entity Framework'ün çakışmaları algılayabilmesi gerekir. Bu nedenle, veritabanını ve veri modelini uygun şekilde yapılandırmanız gerekir. Çakışma algılamayı etkinleştirmeye yönelik bazı seçenekler şunlardır:
Veritabanı tablosunda, bir satırın ne zaman değiştirildiğini belirlemek için kullanılabilecek bir izleme sütunu ekleyin. Daha sonra Entity Framework'ün bu sütunu SQL Update veya Delete komutlarının Where yan tümcesine dahil etmek üzere yapılandırabilirsiniz.
İzleme sütununun veri türü genellikle
rowversion
şeklindedir. Değerrowversion
, satır her güncelleştirildiğinde artırılan sıralı bir sayıdır. Update veya Delete komutunda Where yan tümcesi, izleme sütununun özgün değerini (özgün satır sürümü) içerir. Güncelleştirilmekte olan satır başka bir kullanıcı tarafından değiştirildiyse, sütundakirowversion
değer özgün değerden farklıdır, bu nedenle Update veya Delete deyimi Where yan tümcesi nedeniyle güncelleştirilecek satırı bulamaz. Entity Framework, Güncelleştir veya Sil komutuyla (etkilenen satırların sayısı sıfır olduğunda) hiçbir satırın güncelleştirilmediğini bulduğunda, bunu eşzamanlılık çakışması olarak yorumlar.Entity Framework'i, Update ve Delete komutlarının Where yan tümcesindeki tablodaki her sütunun özgün değerlerini içerecek şekilde yapılandırın.
İlk seçenekte olduğu gibi, satırdaki herhangi bir şey satır ilk okunduktan sonra değiştiyse Where yan tümcesi güncelleştirilecek bir satır döndürmez ve Entity Framework eşzamanlılık çakışması olarak yorumlar. Çok sayıda sütunu olan veritabanı tablolarında bu yaklaşım çok büyük Where yan tümcelerine neden olabilir ve büyük miktarlarda durum korumanızı gerektirebilir. Daha önce belirtildiği gibi, büyük miktarda durumun korunması uygulama performansını etkileyebilir. Bu nedenle bu yaklaşım genellikle önerilmez ve bu öğreticide kullanılan yöntem değildir.
Eşzamanlılık için bu yaklaşımı uygulamak istiyorsanız, özniteliğini ekleyerek
ConcurrencyCheck
eşzamanlılığı izlemek istediğiniz varlıktaki birincil anahtar olmayan tüm özellikleri işaretlemeniz gerekir. Bu değişiklik, Entity Framework'ün Update ve Delete deyimlerinin SQL Where yan tümcesindeki tüm sütunları içermesini sağlar.
Bu öğreticinin geri kalanında Departman varlığına bir rowversion
izleme özelliği ekleyecek, bir denetleyici ve görünüm oluşturacak ve her şeyin düzgün çalıştığını doğrulamak için test yapacaksınız.
İzleme özelliği ekleme
içinde Models/Department.cs
RowVersion adlı bir izleme özelliği ekleyin:
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; }
}
}
Timestamp
özniteliği, bu sütunun veritabanına gönderilen Update ve Delete komutlarının Where yan tümcesine eklendiğini belirtir. SQL Server'ın önceki sürümleri SQL yerine bir SQL timestamp
veri türü kullandığından rowversion
özniteliği çağrılırTimestamp
. için rowversion
.NET türü bir bayt dizisidir.
Akıcı API'yi kullanmayı tercih ediyorsanız, aşağıdaki örnekte gösterildiği gibi izleme özelliğini belirtmek için yöntemini (içindeData/SchoolContext.cs
) kullanabilirsinizIsRowVersion()
:
modelBuilder.Entity<Department>()
.Property(p => p.RowVersion).IsRowVersion();
Bir özellik ekleyerek veritabanı modelini değiştirdiğinizden başka bir geçiş yapmanız gerekir.
Değişikliklerinizi kaydedin ve projeyi derleyin ve ardından komut penceresine aşağıdaki komutları girin:
dotnet ef migrations add RowVersion
dotnet ef database update
Departman denetleyicisi ve görünümleri oluşturma
Daha önce Öğrenciler, Kurslar ve Eğitmenler için yaptığınız gibi bir Bölüm denetleyicisi ve görünümleri iskelesi yapın.
Dosyada DepartmentsController.cs
, bölüm yöneticisi açılan listelerinin yalnızca soyadı yerine eğitmenin tam adını içermesi için "FirstMidName" öğesinin dört oluşumunun tümünü "FullName" olarak değiştirin.
ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID", "FullName", department.InstructorID);
Dizin görünümünü güncelleştirme
yapı iskelesi altyapısı Dizin görünümünde bir RowVersion
sütun oluşturdu, ancak bu alan görüntülenmemelidir.
Views/Departments/Index.cshtml
içindeki kodu aşağıdaki kodla değiştirin.
@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>
Bu işlem başlığı "Departmanlar" olarak değiştirir, sütunu siler RowVersion
ve yöneticinin adı yerine tam adı gösterir.
Düzenleme yöntemlerini güncelleştirme
Hem HttpGet Edit
yönteminde hem de yönteminde Details
ekleyin AsNoTracking
. HttpGet Edit
yönteminde Yönetici için istekli yükleme ekleyin.
var department = await _context.Departments
.Include(i => i.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);
HttpPost Edit
yöntemi için mevcut kodu aşağıdaki kodla değiştirin:
[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);
}
Kod, güncelleştirilecek bölümü okumaya çalışarak başlar. FirstOrDefaultAsync
Yöntem null döndürürse, bölüm başka bir kullanıcı tarafından silindi. Bu durumda kod, bir varlık oluşturmak Department
için gönderilen form değerlerini kullanır, böylece Düzenle sayfası bir hata iletisiyle yeniden dağıtılabilir. Alternatif olarak, departman alanlarını yeniden görüntülemeden yalnızca bir hata iletisi görüntülerseniz varlığı yeniden oluşturmanız Department
gerekmez.
Görünüm, özgün RowVersion
değeri gizli bir alanda depolar ve bu yöntem bu değeri parametresinde rowVersion
alır. çağrısından SaveChanges
önce bu özgün RowVersion
özellik değerini varlığın koleksiyonuna OriginalValues
yerleştirmeniz gerekir.
_context.Entry(departmentToUpdate).Property("RowVersion").OriginalValue = rowVersion;
Ardından Entity Framework bir SQL UPDATE komutu oluşturduğunda, bu komut özgün RowVersion
değere sahip bir satırın arandığı where yan tümcesini içerir. UPDATE komutundan hiçbir satır etkilenmezse (özgün RowVersion
değere sahip satır yoksa), Entity Framework bir DbUpdateConcurrencyException
özel durum oluşturur.
Bu özel durumun catch bloğundaki kod, özel durum nesnesindeki özelliğinden Entries
güncelleştirilmiş değerlere sahip olan etkilenen Department varlığını alır.
var exceptionEntry = ex.Entries.Single();
Koleksiyonun Entries
tek bir EntityEntry
nesnesi olacaktır. Kullanıcı tarafından girilen yeni değerleri ve geçerli veritabanı değerlerini almak için bu nesneyi kullanabilirsiniz.
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
Kod, her sütun için kullanıcının Düzenle sayfasına girdiği değerlerden farklı veritabanı değerlerine sahip özel bir hata iletisi ekler (burada kısa süre için yalnızca bir alan gösterilir).
var databaseValues = (Department)databaseEntry.ToObject();
if (databaseValues.Name != clientValues.Name)
{
ModelState.AddModelError("Name", $"Current value: {databaseValues.Name}");
Son olarak, kod değerini departmentToUpdate
veritabanından alınan yeni değere ayarlarRowVersion
. Bu yeni RowVersion
değer, Düzenle sayfası yeniden görüntülendiğinde ve kullanıcı Kaydet'e bir sonraki tıkladığında, yalnızca Düzenle sayfasının yeniden dağıtılmasından bu yana oluşan eşzamanlılık hataları yakalandığında gizli alanda depolanır.
departmentToUpdate.RowVersion = (byte[])databaseValues.RowVersion;
ModelState.Remove("RowVersion");
ModelState.Remove
Eski RowVersion
değere sahip olduğundan ModelState
deyimi gereklidir. Görünümde, ModelState
her ikisi de mevcut olduğunda bir alanın değeri model özelliği değerlerinden önceliklidir.
Düzenleme görünümünü güncelleştir
Views/Departments/Edit.cshtml
dosyasında aşağıdaki değişiklikleri yapın:
Özelliğin
RowVersion
gizli alanının hemen ardından özellik değerini kaydetmek içinDepartmentID
gizli bir alan ekleyin.Açılan listeye bir "Yönetici Seç" seçeneği ekleyin.
@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");}
}
Eşzamanlılık çakışmalarını test edin
Uygulamayı çalıştırın ve Departmanlar Dizini sayfasına gidin. İngilizce bölümü için Düzenle köprüsüne sağ tıklayın ve Yeni sekmede aç'ı seçin ve ardından İngilizce bölümü için Düzenle köprüsüne tıklayın. İki tarayıcı sekmesi artık aynı bilgileri görüntüler.
İlk tarayıcı sekmesindeki bir alanı değiştirin ve Kaydet'e tıklayın.
Tarayıcı, değiştirilen değere sahip Dizin sayfasını gösterir.
İkinci tarayıcı sekmesindeki bir alanı değiştirin.
Kaydet'e tıklayın. Bir hata iletisi görürsünüz:
Yeniden Kaydet'e tıklayın. İkinci tarayıcı sekmesine girdiğiniz değer kaydedilir. Dizin sayfası görüntülendiğinde kaydedilen değerleri görürsünüz.
Sil sayfasını güncelleştirme
Sil sayfasında Entity Framework, departmanı benzer şekilde düzenleyen başka birinin neden olduğu eşzamanlılık çakışmalarını algılar. HttpGet Delete
yöntemi onay görünümünü görüntülediğinde, görünüm gizli bir alandaki özgün RowVersion
değeri içerir. Bu değer daha sonra kullanıcı silme işlemini onayladığında çağrılan HttpPost Delete
yöntemi tarafından kullanılabilir. Entity Framework SQL DELETE komutunu oluşturduğunda özgün RowVersion
değere sahip bir WHERE yan tümcesi içerir. Komut etkilenen sıfır satırla sonuçlanırsa (silme onay sayfası görüntülendikten sonra satır değiştirildi anlamına gelir), eşzamanlılık özel durumu oluşturulur ve onay sayfasını bir hata iletisiyle yeniden görüntülemek için HttpGet Delete
yöntemi true olarak ayarlanmış bir hata bayrağıyla çağrılır. Satır başka bir kullanıcı tarafından silindiğinden sıfır satır da etkilenmiş olabilir, bu durumda hiçbir hata iletisi görüntülenmez.
Departmanlar denetleyicisindeki Delete yöntemlerini güncelleştirme
içinde DepartmentsController.cs
HttpGet Delete
yöntemini aşağıdaki kodla değiştirin:
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);
}
yöntemi, bir eşzamanlılık hatasından sonra sayfanın yeniden dağıtılıp dağıtılmadığını gösteren isteğe bağlı bir parametre kabul eder. Bu bayrak true ise ve belirtilen bölüm artık yoksa, başka bir kullanıcı tarafından silinmiştir. Bu durumda kod Dizin sayfasına yönlendirilir. Bu bayrak true ise ve bölüm mevcutsa, başka bir kullanıcı tarafından değiştirildi. Bu durumda, kod kullanarak ViewData
görünüme bir hata iletisi gönderir.
HttpPost Delete
yöntemindeki (adlandırılmış DeleteConfirmed
) kodu aşağıdaki kodla değiştirin:
[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 });
}
}
Yeni değiştirdiğiniz yapı iskelesi oluşturulmuş kodda, bu yöntem yalnızca bir kayıt kimliğini kabul etti:
public async Task<IActionResult> DeleteConfirmed(int id)
Bu parametreyi model bağlayıcısı tarafından oluşturulan bir Department
varlık örneğiyle değiştirdiniz. Bu, EF'nin kayıt anahtarına ek olarak RowVers'ion özellik değerine erişmesini sağlar.
public async Task<IActionResult> Delete(Department department)
Eylem yöntemi adını DeleteConfirmed
olarak da değiştirdiniz Delete
. yapı iskelesi oluşturulmuş kod, HttpPost yöntemine benzersiz bir imza vermek için adını DeleteConfirmed
kullandı. (CLR, farklı yöntem parametrelerine sahip olmak için aşırı yüklenmiş yöntemler gerektirir.) artık imzalar benzersiz olduğuna göre, MVC kuralına bağlı kalıp HttpPost ve HttpGet silme yöntemleri için aynı adı kullanabilirsiniz.
Bölüm zaten silinmişse, AnyAsync
yöntem false döndürür ve uygulama yalnızca Index yöntemine geri döner.
Eşzamanlılık hatası yakalanırsa, kod Silme onay sayfasını yeniden görüntüler ve eşzamanlılık hata iletisi görüntülemesi gerektiğini belirten bir bayrak sağlar.
Sil görünümünü güncelleştirme
içinde Views/Departments/Delete.cshtml
, iskelesi oluşturulan kodu, DepartmentID ve RowVersion özellikleri için bir hata iletisi alanı ve gizli alanlar ekleyen aşağıdaki kodla değiştirin. Değişiklikler vurgulanır.
@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>
Bu, aşağıdaki değişiklikleri yapar:
ve
h3
başlıkları arasınah2
bir hata iletisi ekler.FirstMidName değerini Yönetici alanındaki FullName ile değiştirir.
RowVersion alanını kaldırır.
Özelliği için
RowVersion
gizli bir alan ekler.
Uygulamayı çalıştırın ve Departmanlar Dizini sayfasına gidin. İngilizce bölümü için Sil köprüsüne sağ tıklayın ve Yeni sekmede aç'ı seçin, ardından ilk sekmede İngilizce bölümü için Düzenle köprüsüne tıklayın.
İlk pencerede değerlerden birini değiştirin ve Kaydet'e tıklayın:
İkinci sekmede Sil'e tıklayın. Eşzamanlılık hata iletisini görürsünüz ve Departman değerleri şu anda veritabanındaki değerlerle yenilenir.
Sil'e yeniden tıklarsanız, bölümün silindiğini gösteren Dizin sayfasına yönlendirilirsiniz.
Güncelleştirme Ayrıntıları ve Görünüm oluşturma
İsteğe bağlı olarak, ayrıntılar ve Oluşturma görünümlerinde yapı iskelesi oluşturulmuş kodu temizleyebilirsiniz.
RowVersion sütununu silmek ve Yöneticinin tam adını göstermek için içindeki Views/Departments/Details.cshtml
kodu değiştirin.
@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>
Açılan listeye Bir Seç seçeneği eklemek için içindeki Views/Departments/Create.cshtml
kodu değiştirin.
@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");}
}
Kodu alma
Tamamlanan uygulamayı indirin veya görüntüleyin.
Ek kaynaklar
içinde EF Coreeşzamanlılığı işleme hakkında daha fazla bilgi için bkz . Eşzamanlılık çakışmaları.
Sonraki adımlar
Bu öğreticide şunları yaptınız:
- Eşzamanlılık çakışmaları hakkında bilgi edinildi
- İzleme özelliği eklendi
- Oluşturulan Departmanlar denetleyicisi ve görünümleri
- Güncelleştirilmiş Dizin görünümü
- Güncelleştirilmiş Düzenleme yöntemleri
- Güncelleştirilmiş Düzenleme görünümü
- Test edilen eşzamanlılık çakışmaları
- Sil sayfası güncelleştirildi
- Güncelleştirilmiş Ayrıntılar ve Görünüm oluşturma
Eğitmen ve Öğrenci varlıkları için hiyerarşi başına tablo devralmayı uygulamayı öğrenmek için sonraki öğreticiye ilerleyin.
ASP.NET Core