Bagikan melalui


Tutorial: Menangani Konkurensi dengan EF dalam aplikasi MVC 5 ASP.NET

Dalam tutorial sebelumnya, Anda mempelajari cara memperbarui data. Tutorial ini menunjukkan cara menggunakan konkurensi optimis untuk menangani konflik ketika beberapa pengguna memperbarui entitas yang sama secara bersamaan. Anda mengubah halaman web yang berfungsi dengan Department entitas sehingga mereka menangani kesalahan konkurensi. Ilustrasi berikut ini memperlihatkan halaman Edit dan Hapus, termasuk beberapa pesan yang ditampilkan jika terjadi konflik konkurensi.

Cuplikan layar memperlihatkan halaman Edit dengan nilai untuk Nama Departemen, Anggaran, Tanggal Mulai, dan Administrator dengan nilai saat ini disorot.

Cuplikan layar memperlihatkan halaman Hapus untuk rekaman dengan pesan tentang operasi penghapusan dan tombol Hapus.

Di tutorial ini, Anda akan:

  • Pelajari tentang konflik konkurensi
  • Menambahkan konkurensi optimis
  • Mengubah pengontrol Departemen
  • Menguji penanganan konkurensi
  • Memperbarui halaman Hapus

Prasyarat

Konflik konkurensi

Konflik konkurensi terjadi ketika satu pengguna menampilkan data entitas untuk mengeditnya, lalu pengguna lain memperbarui data entitas yang sama sebelum perubahan pengguna pertama ditulis ke database. Jika Anda tidak mengaktifkan deteksi konflik tersebut, siapa pun yang memperbarui database terakhir kali menimpa perubahan pengguna lain. Dalam banyak aplikasi, risiko ini dapat diterima: jika ada beberapa pengguna, atau beberapa pembaruan, atau jika tidak terlalu penting jika beberapa perubahan ditimpa, biaya pemrograman untuk konkurensi mungkin melebihi manfaatnya. Dalam hal ini, Anda tidak perlu mengonfigurasi aplikasi untuk menangani konflik konkurensi.

Konkurensi Pesimis (Penguncian)

Jika aplikasi Anda memang perlu mencegah kehilangan data yang tidak disengaja dalam skenario konkurensi, salah satu cara untuk melakukannya adalah dengan menggunakan kunci database. Ini disebut konkurensi pesimis. Misalnya, sebelum Membaca baris dari database, Anda meminta kunci untuk akses baca-saja atau untuk pembaruan. Jika Anda mengunci baris untuk akses pembaruan, tidak ada pengguna lain yang diizinkan untuk mengunci baris baik untuk akses baca-saja atau perbarui, karena mereka akan mendapatkan salinan data yang sedang dalam proses diubah. Jika Anda mengunci baris untuk akses baca-saja, yang lain juga dapat menguncinya untuk akses baca-saja tetapi tidak untuk pembaruan.

Mengelola kunci memiliki kekurangan. Ini bisa menjadi kompleks untuk diprogram. Ini membutuhkan sumber daya manajemen database yang signifikan, dan dapat menyebabkan masalah performa saat jumlah pengguna aplikasi meningkat. Untuk alasan ini, tidak semua sistem manajemen database mendukung konkurensi pesimis. Kerangka Kerja Entitas tidak menyediakan dukungan bawaan untuk itu, dan tutorial ini tidak menunjukkan kepada Anda cara menerapkannya.

Konkurensi optimis

Alternatif untuk konkurensi pesimis adalah konkurensi optimis. Konkurensi optimis berarti memungkinkan konflik konkurensi terjadi, lalu bereaksi dengan tepat jika terjadi. Misalnya, John menjalankan halaman Edit Departemen, mengubah jumlah Anggaran untuk departemen Bahasa Inggris dari $350.000,00 menjadi $0,00.

Sebelum John mengklik Simpan, Jane menjalankan halaman yang sama dan mengubah bidang Tanggal Mulai dari 1/9/2007 menjadi 8/8/2013.

John mengklik Simpan terlebih dahulu dan melihat perubahannya saat browser kembali ke halaman Indeks, lalu Jane mengklik Simpan. Apa yang terjadi selanjutnya ditentukan oleh cara Anda menangani konflik konkurensi. Beberapa opsi termasuk yang berikut ini:

  • Anda dapat melacak properti mana yang telah dimodifikasi pengguna dan memperbarui hanya kolom yang sesuai dalam database. Dalam skenario contoh, tidak ada data yang akan hilang, karena properti yang berbeda diperbarui oleh dua pengguna. Lain kali seseorang menelusuri departemen Inggris, mereka akan melihat perubahan John dan Jane — tanggal mulai 8/8/2013 dan anggaran nol dolar.

    Metode pembaruan ini dapat mengurangi jumlah konflik yang dapat mengakibatkan kehilangan data, tetapi tidak dapat menghindari kehilangan data jika perubahan yang bersaing dilakukan pada properti entitas yang sama. Apakah Kerangka Kerja Entitas berfungsi dengan cara ini tergantung pada cara Anda menerapkan kode pembaruan Anda. Seringkali tidak praktis dalam aplikasi web, karena dapat mengharuskan Anda mempertahankan status dalam jumlah besar untuk melacak semua nilai properti asli untuk entitas serta nilai baru. Mempertahankan status dalam jumlah besar dapat memengaruhi performa aplikasi karena memerlukan sumber daya server atau harus disertakan dalam halaman web itu sendiri (misalnya, di bidang tersembunyi) atau di cookie.

  • Kau bisa membiarkan perubahan Jane menimpa perubahan John. Lain kali seseorang menelusuri departemen Bahasa Inggris, mereka akan melihat 8/8/2013 dan nilai $350,000.00 yang dipulihkan. Ini disebut skenario Client Wins atau Last in Wins . (Semua nilai dari klien lebih diutamakan daripada apa yang ada di penyimpanan data.) Seperti yang disebutkan dalam pengenalan bagian ini, jika Anda tidak melakukan pengkodan apa pun untuk penanganan konkurensi, ini akan terjadi secara otomatis.

  • Anda dapat mencegah perubahan Jane diperbarui dalam database. Biasanya, Anda akan menampilkan pesan kesalahan, menunjukkan status data saat ini, dan memungkinkannya untuk menerapkan kembali perubahannya jika dia masih ingin membuatnya. Ini disebut skenario Store Wins . (Nilai penyimpanan data lebih diutamakan daripada nilai yang dikirimkan oleh klien.) Anda akan menerapkan skenario Store Wins dalam tutorial ini. Metode ini memastikan bahwa tidak ada perubahan yang ditimpa tanpa pengguna diberitahu tentang apa yang terjadi.

Mendeteksi Konflik Konkurensi

Anda dapat mengatasi konflik dengan menangani pengecualian OptimisConcurrencyException yang dilemparkan Kerangka Kerja Entitas. Untuk mengetahui kapan harus melemparkan pengecualian ini, Kerangka Kerja Entitas harus dapat mendeteksi konflik. Oleh karena itu, Anda harus mengonfigurasi database dan model data dengan tepat. Beberapa opsi untuk mengaktifkan deteksi konflik meliputi yang berikut ini:

  • Dalam tabel database, sertakan kolom pelacakan yang dapat digunakan untuk menentukan kapan baris telah diubah. Anda kemudian dapat mengonfigurasi Kerangka Kerja Entitas untuk menyertakan kolom tersebut Where dalam klausul SQL Update atau Delete perintah.

    Jenis data kolom pelacakan biasanya rowversion. Nilai rowversion adalah angka berurutan yang bertahap setiap kali baris diperbarui. Dalam perintah Update atau Delete , Where klausul menyertakan nilai asli kolom pelacakan (versi baris asli). Jika baris yang diperbarui telah diubah oleh pengguna lain, nilai dalam rowversion kolom berbeda dari nilai asli, sehingga Update pernyataan atau Delete tidak dapat menemukan baris untuk diperbarui karena Where klausul. Ketika Kerangka Kerja Entitas menemukan bahwa tidak ada baris yang telah diperbarui oleh Update perintah atau Delete (yaitu, ketika jumlah baris yang terpengaruh adalah nol), itu menafsirkan bahwa sebagai konflik konkurensi.

  • Konfigurasikan Kerangka Kerja Entitas untuk menyertakan nilai asli setiap kolom dalam tabel dalam Where klausul Update dan Delete perintah.

    Seperti pada opsi pertama, jika apa pun dalam baris telah berubah sejak baris pertama kali dibaca, Where klausa tidak akan mengembalikan baris untuk diperbarui, yang ditafsirkan oleh Kerangka Kerja Entitas sebagai konflik konkurensi. Untuk tabel database yang memiliki banyak kolom, pendekatan ini dapat menghasilkan klausa yang sangat besar Where , dan dapat mengharuskan Anda mempertahankan status dalam jumlah besar. Seperti disebutkan sebelumnya, mempertahankan status dalam jumlah besar dapat memengaruhi performa aplikasi. Oleh karena itu pendekatan ini umumnya tidak disarankan, dan bukan metode yang digunakan dalam tutorial ini.

    Jika Anda ingin menerapkan pendekatan ini ke konkurensi, Anda harus menandai semua properti kunci non-primer di entitas yang ingin Anda lacak konkurensinya dengan menambahkan atribut ConcurrencyCheck ke properti tersebut. Perubahan itu memungkinkan Kerangka Kerja Entitas untuk menyertakan semua kolom dalam klausa UPDATE pernyataan SQLWHERE.

Di sisa tutorial ini, Anda akan menambahkan properti pelacakan rowversion ke Department entitas, membuat pengontrol dan tampilan, dan menguji untuk memverifikasi bahwa semuanya berfungsi dengan benar.

Menambahkan konkurensi optimis

Di Model\Department.cs, tambahkan properti pelacakan bernama 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 Tanda waktu menentukan bahwa kolom ini akan disertakan dalam Where klausul Update dan Delete perintah yang dikirim ke database. Atribut ini disebut Tanda Waktu karena versi SQL Server sebelumnya menggunakan jenis data tanda waktu SQL sebelum rowversion SQL menggantikannya. Jenis .Net untuk rowversion adalah array byte.

Jika Anda lebih suka menggunakan API yang fasih, Anda dapat menggunakan metode IsConcurrencyToken untuk menentukan properti pelacakan, seperti yang ditunjukkan dalam contoh berikut:

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

Dengan menambahkan properti, Anda mengubah model database, jadi Anda perlu melakukan migrasi lain. Di Package Manager Console (PMC), masukkan perintah berikut:

Add-Migration RowVersion
Update-Database

Mengubah pengontrol Departemen

Di Controllers\DepartmentController.cs, tambahkan using pernyataan:

using System.Data.Entity.Infrastructure;

Dalam file DepartmentController.cs, ubah keempat kemunculan "LastName" menjadi "FullName" sehingga daftar drop-down administrator departemen akan berisi nama lengkap instruktur daripada hanya nama belakang.

ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName");

Ganti kode yang HttpPost Edit ada untuk metode dengan kode berikut:

[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 Jika metode mengembalikan null, departemen dihapus oleh pengguna lain. Kode yang ditampilkan menggunakan nilai formulir yang diposting untuk membuat entitas departemen sehingga halaman Edit dapat diputar ulang dengan pesan kesalahan. Sebagai alternatif, Anda tidak perlu membuat ulang entitas departemen jika Anda hanya menampilkan pesan kesalahan tanpa memutar ulang bidang departemen.

Tampilan menyimpan nilai asli RowVersion di bidang tersembunyi, dan metode menerimanya dalam rowVersion parameter . Sebelum memanggil SaveChanges, Anda harus menempatkan nilai properti asli RowVersion tersebut OriginalValues dalam koleksi untuk entitas. Kemudian ketika Kerangka Kerja Entitas membuat perintah SQL UPDATE , perintah tersebut akan menyertakan WHERE klausa yang mencari baris yang memiliki nilai asli RowVersion .

Jika tidak ada baris yang dipengaruhi oleh UPDATE perintah (tidak ada baris yang memiliki nilai asli RowVersion ), Kerangka Kerja Entitas melemparkan DbUpdateConcurrencyException pengecualian, dan kode di catch blok mendapatkan entitas yang terpengaruh Department dari objek pengecualian.

var entry = ex.Entries.Single();

Objek ini memiliki nilai baru yang dimasukkan oleh pengguna di propertinya Entity , dan Anda bisa mendapatkan nilai yang dibaca dari database dengan memanggil GetDatabaseValues metode .

var clientValues = (Department)entry.Entity;
var databaseEntry = entry.GetDatabaseValues();

Metode GetDatabaseValues mengembalikan null jika seseorang telah menghapus baris dari database; jika tidak, Anda harus melemparkan objek yang dikembalikan ke Department kelas untuk mengakses Department properti. (Karena Anda sudah memeriksa penghapusan, databaseEntry akan null hanya jika departemen dihapus setelah FindAsync dijalankan dan sebelum SaveChanges dijalankan.)

if (databaseEntry == null)
{
    ModelState.AddModelError(string.Empty,
        "Unable to save changes. The department was deleted by another user.");
}
else
{
    var databaseValues = (Department)databaseEntry.ToObject();

Selanjutnya, kode menambahkan pesan kesalahan kustom untuk setiap kolom yang memiliki nilai database yang berbeda dari apa yang dimasukkan pengguna di halaman Edit:

if (databaseValues.Name != currentValues.Name)
    ModelState.AddModelError("Name", "Current value: " + databaseValues.Name);
    // ...

Pesan kesalahan yang lebih panjang menjelaskan apa yang terjadi dan apa yang harus dilakukan tentang hal itu:

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.");

Terakhir, kode menetapkan RowVersion nilai Department objek ke nilai baru yang diambil dari database. Nilai baru RowVersion ini akan disimpan di bidang tersembunyi ketika halaman Edit diputar ulang, dan saat berikutnya pengguna mengklik Simpan, hanya kesalahan konkurensi yang terjadi karena pemutaran ulang halaman Edit akan tertangkap.

Di Views\Department\Edit.cshtml, tambahkan bidang tersembunyi untuk menyimpan RowVersion nilai properti, segera mengikuti bidang tersembunyi untuk DepartmentID properti:

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

Menguji penanganan konkurensi

Jalankan situs dan klik Departemen.

Klik kanan edit hyperlink untuk departemen bahasa Inggris dan pilih Buka di tab baru, lalu klik edit hyperlink untuk departemen bahasa Inggris. Dua tab menampilkan informasi yang sama.

Ubah bidang di tab browser pertama dan klik Simpan.

Browser memperlihatkan halaman Indeks dengan nilai yang diubah.

Ubah bidang di tab browser kedua dan klik Simpan. Anda melihat pesan kesalahan:

Cuplikan layar memperlihatkan halaman Edit dengan pesan yang menjelaskan bahwa operasi dibatalkan karena nilainya telah diubah oleh pengguna lain.

Klik Simpan lagi. Nilai yang Anda masukkan di tab browser kedua disimpan bersama dengan nilai asli data yang Anda ubah di browser pertama. Anda melihat nilai yang disimpan saat halaman Indeks muncul.

Memperbarui halaman Hapus

Untuk halaman Hapus, Kerangka Kerja Entitas mendeteksi konflik konkurensi yang disebabkan oleh orang lain yang mengedit departemen dengan cara yang sama. HttpGet Delete Saat metode menampilkan tampilan konfirmasi, tampilan menyertakan nilai asli RowVersion dalam bidang tersembunyi. Nilai tersebut kemudian tersedia untuk HttpPost Delete metode yang dipanggil ketika pengguna mengonfirmasi penghapusan. Saat Kerangka Kerja Entitas membuat perintah SQL DELETE , kerangka kerja menyertakan WHERE klausul dengan nilai asli RowVersion . Jika perintah menghasilkan nol baris yang terpengaruh (artinya baris diubah setelah halaman Konfirmasi penghapusan ditampilkan), pengecualian konkurensi dilemparkan, dan HttpGet Delete metode dipanggil dengan bendera kesalahan yang diatur ke true untuk memutar ulang halaman konfirmasi dengan pesan kesalahan. Ada kemungkinan juga bahwa nol baris terpengaruh karena baris dihapus oleh pengguna lain, jadi dalam hal ini pesan kesalahan yang berbeda ditampilkan.

Di DepartmentController.cs, ganti HttpGet Delete metode dengan kode berikut:

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);
}

Metode ini menerima parameter opsional yang menunjukkan apakah halaman sedang diputar ulang setelah kesalahan konkurensi. Jika bendera ini adalah true, pesan kesalahan dikirim ke tampilan menggunakan ViewBag properti .

Ganti kode dalam HttpPost Delete metode (bernama DeleteConfirmed) dengan kode berikut:

[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);
    }
}

Dalam kode perancah yang baru saja Anda ganti, metode ini hanya menerima ID rekaman:

public async Task<ActionResult> DeleteConfirmed(int id)

Anda telah mengubah parameter ini menjadi instans entitas yang Department dibuat oleh pengikat model. Ini memberi Anda akses ke RowVersion nilai properti selain kunci rekaman.

public async Task<ActionResult> Delete(Department department)

Anda juga telah mengubah nama metode tindakan dari DeleteConfirmed menjadi Delete. Kode perancah bernama HttpPost Delete metode DeleteConfirmed untuk memberikan HttpPost metode tanda tangan unik. ( CLR memerlukan metode kelebihan beban untuk memiliki parameter metode yang berbeda.) Sekarang setelah tanda tangan unik, Anda dapat tetap dengan konvensi MVC dan menggunakan nama yang sama untuk HttpPost metode dan HttpGet hapus.

Jika kesalahan konkurensi tertangkap, kode akan menampilkan kembali halaman Konfirmasi penghapusan dan menyediakan bendera yang menunjukkan bahwa kode harus menampilkan pesan kesalahan konkurensi.

Di Views\Department\Delete.cshtml, ganti kode perancah dengan kode berikut yang menambahkan bidang pesan kesalahan dan bidang tersembunyi untuk properti DepartmentID dan RowVersion. Perubahan disorot.

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

Kode ini menambahkan pesan kesalahan antara judul h2 dan h3 :

<p class="error">@ViewBag.ConcurrencyErrorMessage</p>

Ini menggantikan LastName dengan FullName di Administrator bidang :

<dt>
  Administrator
</dt>
<dd>
  @Html.DisplayFor(model => model.Administrator.FullName)
</dd>

Terakhir, ia menambahkan bidang tersembunyi untuk DepartmentID properti dan RowVersion setelah Html.BeginForm pernyataan:

@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)

Jalankan halaman Indeks Departemen. Klik kanan hyperlink Hapus untuk departemen bahasa Inggris dan pilih Buka di tab baru, lalu di tab pertama klik edit hyperlink untuk departemen bahasa Inggris.

Di jendela pertama, ubah salah satu nilai, dan klik Simpan.

Halaman Indeks mengonfirmasi perubahan.

Di tab kedua, klik Hapus.

Anda melihat pesan kesalahan konkurensi, dan nilai Departemen di-refresh dengan apa yang saat ini ada di database.

Department_Delete_confirmation_page_with_concurrency_error

Jika Anda mengklik Hapus lagi, Anda dialihkan ke halaman Indeks, yang menunjukkan bahwa departemen telah dihapus.

Mendapatkan kode

Unduh Proyek Yang Selesai

Sumber Daya Tambahan:

Tautan ke sumber daya Kerangka Kerja Entitas lainnya dapat ditemukan di Akses Data ASP.NET - Sumber Daya yang Direkomendasikan.

Untuk informasi tentang cara lain untuk menangani berbagai skenario konkurensi, lihat Pola Konkurensi Optimis dan Bekerja dengan Nilai Properti di MSDN. Tutorial berikutnya menunjukkan cara menerapkan warisan tabel per hierarki untuk Instructor entitas dan Student .

Langkah berikutnya

Di tutorial ini, Anda akan:

  • Dipelajari tentang konflik konkurensi
  • Menambahkan konkurensi optimis
  • Pengontrol Departemen yang Dimodifikasi
  • Penanganan konkurensi yang diuji
  • Memperbarui halaman Hapus

Lanjutkan ke artikel berikutnya untuk mempelajari cara menerapkan pewarisan dalam model data.