Menangani Konflik Konkurensi (EF6)
Konkurensi optimis melibatkan upaya optimis untuk menyimpan entitas Anda ke database dengan harapan bahwa data di sana tidak berubah sejak entitas dimuat. Jika ternyata data telah berubah, maka pengecualian dilemparkan dan Anda harus mengatasi konflik sebelum mencoba menyimpan lagi. Topik ini membahas cara menangani pengecualian tersebut dalam Kerangka Kerja Entitas. Teknik yang ditunjukkan dalam topik ini berlaku sama untuk model yang dibuat dengan Perancang EF dan Code First.
Posting ini bukan tempat yang tepat untuk diskusi lengkap tentang konkurensi optimis. Bagian di bawah ini mengasumsikan beberapa pengetahuan tentang resolusi konkurensi dan menunjukkan pola untuk tugas umum.
Banyak dari pola ini menggunakan topik yang dibahas dalam Bekerja dengan Nilai Properti.
Mengatasi masalah konkurensi saat Anda menggunakan asosiasi independen (di mana kunci asing tidak dipetakan ke properti di entitas Anda) jauh lebih sulit daripada ketika Anda menggunakan asosiasi kunci asing. Oleh karena itu jika Anda akan melakukan resolusi konkurensi dalam aplikasi Anda, disarankan agar Anda selalu memetakan kunci asing ke dalam entitas Anda. Semua contoh di bawah ini mengasumsikan bahwa Anda menggunakan asosiasi kunci asing.
DbUpdateConcurrencyException dilemparkan oleh SaveChanges ketika pengecualian konkurensi optimis terdeteksi saat mencoba menyimpan entitas yang menggunakan asosiasi kunci asing.
Menyelesaikan pengecualian konkurensi optimis dengan Muat Ulang (database menang)
Metode Muat Ulang dapat digunakan untuk menimpa nilai entitas saat ini dengan nilai sekarang dalam database. Entitas kemudian biasanya diberikan kembali kepada pengguna dalam beberapa formulir dan mereka harus mencoba membuat perubahan mereka lagi dan menyimpan ulang. Contohnya:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update the values of the entity that failed to save from the store
ex.Entries.Single().Reload();
}
} while (saveFailed);
}
Cara yang baik untuk mensimulasikan pengecualian konkurensi adalah dengan mengatur titik henti pada panggilan SaveChanges lalu memodifikasi entitas yang sedang disimpan dalam database menggunakan alat lain seperti SQL Server Management Studio. Anda juga dapat menyisipkan baris sebelum SaveChanges untuk memperbarui database secara langsung menggunakan SqlCommand. Contohnya:
context.Database.SqlCommand(
"UPDATE dbo.Blogs SET Name = 'Another Name' WHERE BlogId = 1");
Metode Entri pada DbUpdateConcurrencyException mengembalikan instans DbEntityEntry untuk entitas yang gagal diperbarui. (Properti ini saat ini selalu mengembalikan satu nilai untuk masalah konkurensi. Ini dapat mengembalikan beberapa nilai untuk pengecualian pembaruan umum.) Alternatif untuk beberapa situasi mungkin adalah mendapatkan entri untuk semua entitas yang mungkin perlu dimuat ulang dari database dan memanggil muat ulang untuk masing-masing entitas ini.
Menyelesaikan pengecualian konkurensi optimis saat klien menang
Contoh di atas yang menggunakan Muat Ulang terkadang disebut kemenangan database atau penyimpanan menang karena nilai dalam entitas ditimpa oleh nilai dari database. Terkadang Anda mungkin ingin melakukan kebalikannya dan menimpa nilai dalam database dengan nilai yang saat ini ada di entitas. Ini kadang-kadang disebut klien menang dan dapat dilakukan dengan mendapatkan nilai database saat ini dan mengaturnya sebagai nilai asli untuk entitas. (Lihat Bekerja dengan Nilai Properti untuk informasi tentang nilai saat ini dan asli.) Misalnya:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update original values from the database
var entry = ex.Entries.Single();
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
}
} while (saveFailed);
}
Resolusi kustom pengecualian konkurensi optimis
Terkadang Anda mungkin ingin menggabungkan nilai yang saat ini ada di database dengan nilai yang saat ini ada di entitas. Ini biasanya memerlukan beberapa logika kustom atau interaksi pengguna. Misalnya, Anda dapat menyajikan formulir kepada pengguna yang berisi nilai saat ini, nilai dalam database, dan sekumpulan nilai terselesaikan default. Pengguna kemudian akan mengedit nilai yang diselesaikan seperlunya dan itu akan menjadi nilai yang diselesaikan ini yang disimpan ke database. Ini dapat dilakukan menggunakan objek DbPropertyValues yang dikembalikan dari CurrentValues dan GetDatabaseValues pada entri entitas. Contohnya:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Get the current entity values and the values in the database
var entry = ex.Entries.Single();
var currentValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValues = databaseValues.Clone();
// Have the user choose what the resolved values should be
HaveUserResolveConcurrency(currentValues, databaseValues, resolvedValues);
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
}
} while (saveFailed);
}
public void HaveUserResolveConcurrency(DbPropertyValues currentValues,
DbPropertyValues databaseValues,
DbPropertyValues resolvedValues)
{
// Show the current, database, and resolved values to the user and have
// them edit the resolved values to get the correct resolution.
}
Resolusi kustom pengecualian konkurensi optimis menggunakan objek
Kode di atas menggunakan instans DbPropertyValues untuk melewati nilai saat ini, database, dan terselesaikan. Terkadang mungkin lebih mudah untuk menggunakan instans jenis entitas Anda untuk ini. Ini dapat dilakukan menggunakan metode ToObject dan SetValues dari DbPropertyValues. Contohnya:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Get the current entity values and the values in the database
// as instances of the entity type
var entry = ex.Entries.Single();
var databaseValues = entry.GetDatabaseValues();
var databaseValuesAsBlog = (Blog)databaseValues.ToObject();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValuesAsBlog = (Blog)databaseValues.ToObject();
// Have the user choose what the resolved values should be
HaveUserResolveConcurrency((Blog)entry.Entity,
databaseValuesAsBlog,
resolvedValuesAsBlog);
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValuesAsBlog);
}
} while (saveFailed);
}
public void HaveUserResolveConcurrency(Blog entity,
Blog databaseValues,
Blog resolvedValues)
{
// Show the current, database, and resolved values to the user and have
// them update the resolved values to get the correct resolution.
}