Öğ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.

Department Edit page

Department Delete page

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

Ön koş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.

Changing budget to 0

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.

Changing start date to 2013

Jane önce Kaydet'e tıklar ve tarayıcı Dizin sayfasına döndüğünde değişikliğini görür.

Budget changed to zero

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ğer rowversion , 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ütundaki rowversion 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.csRowVersion 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) kullanabilirsinizIsConcurrencyToken:

modelBuilder.Entity<Department>()
    .Property(p => p.RowVersion).IsConcurrencyToken();

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.

Scaffold Department

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 istrator 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

içinde Views/Departments/Edit.cshtmlaşağıdaki değişiklikleri yapın:

  • Özelliğin RowVersion gizli alanının hemen ardından özellik değerini kaydetmek için DepartmentID gizli bir alan ekleyin.

  • Açılan listeye "Yönetici istrator'ı seçin" seçeneğini 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.

Department Edit page 1 after change

Tarayıcı, değiştirilen değere sahip Dizin sayfasını gösterir.

İkinci tarayıcı sekmesindeki bir alanı değiştirin.

Department Edit page 2 after change

Kaydet'e tıklayın. Bir hata iletisi görürsünüz:

Department Edit page error message

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.csHttpGet 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 ViewDatagö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ına h2 bir hata iletisi ekler.

  • FirstMidName değerini Yönetici istrator 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:

Department Edit page after change before delete

İ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.

Department Delete confirmation page with concurrency error

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 için içindeki Views/Departments/Details.cshtml kodu değiştirin ve Yönetici istrator'ın tam adını gösterin.

@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.