Pada artikel sebelumnya, saya menyajikan beberapa masalah yang mungkin Anda temui saat menggunakan LINQ ke SQL untuk operasi pembaruan. Sebenarnya, ini bukan masalah yang saya temui sendiri. Ketika saya mencari jawaban di Internet, saya menemukan banyak orang telah menerbitkan artikel serupa tentang topik ini. Namun yang membuat saya kurang puas adalah meskipun mereka mengangkat masalah tersebut, mereka tidak melakukan analisa secara detail. Mereka hanya memberikan solusi (seperti menambahkan kolom RowVersion, menghapus asosiasi, dll), namun tidak menjelaskan mengapa mereka harus melakukan hal tersebut. . Ini juga merupakan niat awal penulisan artikel sebelumnya. Saya berharap dapat menemukan solusi masalah selangkah demi selangkah melalui analisis kode sumber LINQ ke SQL. Artikel ini akan membahas cara-cara tersebut satu per satu.
Opsi 1: Penugasan Ulang Dalam kerangka open source Ezsocio oleh TerryLee, Anytao, Ding Xue dan lainnya, penugasan ulang diadopsi di beberapa tempat. Di dalam metode Pembaruan, dapatkan entitas dalam database berdasarkan kunci utama, lalu tetapkan nilai ke propertinya satu per satu dengan entitas dalam parameter.
public void UpdateProfile(Profil p)
{
menggunakan (RepositoryContext db = RepositoryContext baru())
{
var profil = db.GetTable<Profil>().Pertama<Profil>(u => u.ID == p.ID);
profil.Ulang Tahun = p.Ulang Tahun;
profil.Jenis Kelamin = p.Jenis Kelamin;
profil.Kampung Halaman = p.Kampung Halaman;
profil.MSN = p.MSN;
profil.Nama Panggilan = p.Nama Panggilan;
profil.Nomor Telepon = p.Nomor Telepon;
profil.QQ = p.QQ;
profil.Negara Bagian = p.Negara Bagian;
profile.TrueName = p.TrueName;
profil.StateRefreshTime = p.StateRefreshTime;
profil.Avatar = p.Avatar;
profil.Situs Web = p.Situs Web;
db.KirimPerubahan();
}
}
Saudara Yang Guo juga memberikan metode refleksi untuk solusi ini guna mencapai penyalinan nilai atribut secara otomatis.
Tapi menurut saya pribadi ini adalah skema yang menghindari kenyataan dan menghindari kenyataan. Skema ini tidak menggunakan API yang disediakan oleh LINQ ke SQL untuk operasi pembaruan, tetapi mengadopsi strategi memutar. Ini sebenarnya kompromi, apakah karena cara Attachnya “tidak mudah digunakan”, sehingga tidak kita gunakan? hehe.
Opsi 2: Nonaktifkan pelacakan objek. Dalam hal ini, lea mengusulkan agar pembaruan yang benar dapat dicapai dengan menyetel properti ObjectTrackingEnabled dari DataContext ke false.
Produk publik DapatkanProduk(int id)
{
NorthwindDataContext db = baru NorthwindDataContext();
db.ObjectTrackingEnabled = salah;
return db.Products.SingleOrDefault(p => p.ProductID == id);
}
Tidak ada perubahan kode lainnya.
Mengapa dapat diperbarui secara normal setelah menonaktifkan pelacakan objek? Mari temukan jawabannya dari kode sumbernya.
bool publik ObjectTrackingEnabled
{
mendapatkan
{
ini.PeriksaBuang();
kembalikan ini.objectTrackingEnabled;
}
mengatur
{
ini.PeriksaBuang();
jika (ini.Layanan.HasCachedObjects)
{
melempar System.Data.Linq.Error.OptionsCannotBeModifiedAfterQuery();
}
this.objectTrackingEnabled = nilai;
jika (!ini.objectTrackingEnabled)
{
this.deferredLoadingEnabled = salah;
}
ini.layanan.ResetServices();
}
}
Ternyata ketika ObjectTrackingEnabled disetel ke false, DeferredLoadingEnabled akan disetel ke false pada saat yang bersamaan. Dengan cara ini, saat menjalankan kueri, tidak ada data yang memerlukan kueri tertunda yang akan dimuat untuk entitas, sehingga tidak ada pengecualian yang akan ditampilkan selama Lampirkan (lihat analisis di artikel sebelumnya).
Di MSDN kita juga mendapatkan informasi berguna berikut: Menyetel properti ObjectTrackingEnable ke false dapat meningkatkan kinerja pengambilan karena mengurangi jumlah item yang akan dilacak. Ini adalah fitur yang sangat menggoda.
Namun, saat menonaktifkan pelacakan objek, perhatian khusus harus diberikan pada dua poin: (1) Ini harus dinonaktifkan sebelum menjalankan kueri. (2) Setelah dinonaktifkan, metode Lampirkan dan KirimPerubahan tidak dapat dipanggil lagi. Jika tidak, pengecualian akan diberikan.
Opsi 3: Hapus asosiasi. Metode yang lemah telah diperkenalkan di artikel sebelumnya, yaitu mengatur Kategori yang terkait dengan Produk secara manual ke null dalam metode GetProduct. Kita dapat mengekstrak bagian kode ini dan memasukkannya ke dalam metode Detach. Karena Detach ini adalah metode entitas, sebagian kelas dapat digunakan:
Produk kelas parsial publik
{
kekosongan publik Lepaskan()
{
this._Category = default(EntityRef<Kategori>);
}
}
Kategori kelas parsial publik
{
kekosongan publik Lepaskan()
{
foreach (var produk di ini.Produk)
{
produk.Lepaskan();
}
}
}Tetapi metode mendefinisikan Detach untuk setiap entitas terlalu rumit. Ketika jumlah entitas meningkat, hubungan menjadi semakin kompleks, dan atribut yang hilang mudah muncul. Zhang Yi mengusulkan metode yang sangat elegan untuk mengabstraksi logika ini menggunakan refleksi:
private void Detach (entitas TEntity)
{
foreach (FieldInfo fi di entitas.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
{
if (fi.FieldType.ToString().Contains("EntityRef"))
{
var nilai = fi.GetValue(entitas);
jika (nilai != nol)
{
fi.SetValue(entitas, null);
}
}
if (fi.FieldType.ToString().Contains("EntitySet"))
{
var nilai = fi.GetValue(entitas);
jika (nilai != nol)
{
MethodInfo mi = value.GetType().GetMethod("Hapus");
jika (mi != nol)
{
mi.Invoke(nilai, null);
}
fi.SetValue(entitas, nilai);
}
}
}
}
Beberapa orang juga berpikir bahwa peristiwa PropertyChanging dan PropertyChanged harus disetel ke null selama Detach, namun ide keseluruhannya sama.
Opsi 4: Gunakan delegasi. Ini adalah metode yang diberikan oleh ZC29 di komentar artikel terakhir saya.
public void UpdateProductWithDelegate(Predikat Ekspresi<Func<Produk, bool>>, Tindakan<Produk> tindakan)
{
NorthwindDataContext db = baru NorthwindDataContext();
var product = db.Products.SingleOrDefault(predikat);
tindakan (produk);
db.KirimPerubahan();
}
//Kode klien
Repositori ProductRepository = ProductRepository baru();
repositori.UpdateProductWithDelegate(p => p.ProductID == 1, p =>
{
p.Nama Produk = "Diubah";
});
Gunakan ekspresi Lambda untuk menyematkan logika GetProduct ke dalam UpdateProduct, dan gunakan delegasi untuk menunda eksekusi logika pembaruan. Ini secara cerdik menempatkan pencarian dan pembaruan ke dalam DataContext, sehingga melewati Lampirkan. Namun, API metode ini agak terlalu rumit dan memerlukan pemrogram klien tingkat tinggi. Selain itu, logika Get harus dijalankan lagi di Pembaruan. Meskipun kehilangan kinerjanya minimal, hal ini sepertinya selalu memberikan perasaan bahwa ini belum cukup KERING.
Opsi 5: Gunakan pernyataan UPDATE Dalam kode sumber Ezsocio, saya menemukan metode RepositoryBase.UpdateEntity. Penyambungan pernyataan SQL dilakukan di dalam metode, dan hanya kolom yang diubah yang akan diperbarui. Karena ITable tidak lagi digunakan di sini dan diperlukan dukungan kerangka kerja penuh, tidak ada komentar lebih lanjut yang akan dibuat. Silakan merujuk ke kode sumber Ezsocio untuk detailnya.
Ringkasan Artikel ini mencantumkan beberapa solusi yang saya temukan di Internet dalam beberapa hari terakhir. Semuanya memiliki pro dan kontra. Pada artikel selanjutnya, saya akan membandingkan kinerja metode-metode tersebut untuk menemukan solusi optimal.