Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Petunjuk / Saran
Kode dalam dokumen ini dapat ditemukan di GitHub sebagai sampel yang dapat dijalankan.
Latar Belakang
Pelacakan perubahan berarti bahwa EF Core secara otomatis menentukan perubahan apa yang dilakukan oleh aplikasi pada instans entitas yang dimuat, sehingga perubahan tersebut dapat disimpan kembali ke database saat SaveChanges dipanggil. EF Core biasanya melakukan ini dengan mengambil rekam jepret instans saat dimuat dari database, dan membandingkan rekam jepret tersebut dengan instans yang diserahkan ke aplikasi.
EF Core hadir dengan logika bawaan untuk rekam jepret dan membandingkan sebagian besar jenis standar yang digunakan dalam database, sehingga pengguna biasanya tidak perlu khawatir tentang topik ini. Namun, ketika properti dipetakan melalui pengonversi nilai, EF Core perlu melakukan perbandingan pada jenis pengguna arbitrer, yang mungkin kompleks. Secara default, EF Core menggunakan perbandingan kesetaraan default yang ditentukan oleh jenis (misalnya Equals metode); untuk rekam jepret, jenis nilai disalin untuk menghasilkan rekam jepret, sementara untuk jenis referensi tidak ada penyalinan yang terjadi, dan instans yang sama digunakan sebagai rekam jepret.
Dalam kasus di mana perilaku perbandingan yang telah terpasang tidak sesuai, pengguna dapat memberikan komparator nilai, yang mengandung logika untuk pengambilan gambar, membandingkan, dan menghitung kode hash. Misalnya, berikut ini menyiapkan konversi nilai untuk properti List<int> sehingga nilai tersebut diubah menjadi string JSON dalam database, serta mendefinisikan pembanding nilai yang sesuai.
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyListProperty)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
new ValueComparer<List<int>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()));
Lihat kelas yang dapat diubah di bawah ini untuk detail lebih lanjut.
Perhatikan bahwa pembanding nilai juga digunakan saat menentukan apakah dua nilai kunci sama saat menyelesaikan hubungan; ini dijelaskan di bawah ini.
Perbandingan dangkal vs. mendalam
Untuk jenis nilai kecil dan tidak dapat diubah seperti int, logika default EF Core berfungsi dengan baik: nilai disalin as-is saat direkam jepret, dan dibandingkan dengan perbandingan kesetaraan bawaan jenis. Saat menerapkan pembanding nilai Anda sendiri, penting untuk mempertimbangkan apakah logika perbandingan nilai secara mendalam atau dangkal (dan pengambilan cuplikan) sesuai.
Pertimbangkan array byte, yang bisa sebesar mungkin. Ini dapat dibandingkan:
- Menggunakan referensi, sehingga perbedaan hanya akan terdeteksi apabila ada penggunaan array byte baru.
- Dengan perbandingan mendalam, sehingga perubahan byte dalam array dapat terdeteksi.
Secara default, EF Core menerapkan metode pertama untuk array byte non-kunci. Artinya, hanya referensi yang dibandingkan dan perubahan hanya terdeteksi ketika array byte yang ada diganti dengan yang baru. Ini adalah keputusan pragmatis yang menghindari penyalinan seluruh array dan membandingkannya per byte saat mengeksekusi SaveChanges. Ini berarti bahwa skenario umum mengganti, katakanlah, satu gambar dengan gambar lain ditangani dengan cara yang berkinerja.
Di sisi lain, kesetaraan referensi tidak akan berfungsi ketika array byte digunakan untuk mewakili kunci biner, karena sangat tidak mungkin properti FK diatur ke instans yang sama dengan properti PK yang perlu dibandingkan. Oleh karena itu, EF Core menggunakan perbandingan mendalam untuk array byte yang bertindak sebagai kunci; ini tidak mungkin memiliki dampak besar pada kinerja karena kunci biner biasanya pendek.
Perhatikan bahwa perbandingan dan logika rekam jepret yang dipilih harus sesuai satu sama lain: perbandingan mendalam memerlukan rekam jepret mendalam agar berfungsi dengan benar.
Kelas sederhana yang tidak dapat diubah
Pertimbangkan properti yang menggunakan pengonversi nilai untuk memetakan kelas sederhana dan tidak dapat diubah.
public sealed class ImmutableClass
{
public ImmutableClass(int value)
{
Value = value;
}
public int Value { get; }
private bool Equals(ImmutableClass other)
=> Value == other.Value;
public override bool Equals(object obj)
=> ReferenceEquals(this, obj) || obj is ImmutableClass other && Equals(other);
public override int GetHashCode()
=> Value.GetHashCode();
}
modelBuilder
.Entity<MyEntityType>()
.Property(e => e.MyProperty)
.HasConversion(
v => v.Value,
v => new ImmutableClass(v));
Properti jenis ini tidak memerlukan perbandingan atau rekam jepret khusus karena:
- Kesetaraan dikesampingkan sehingga instance yang berbeda dapat dibandingkan dengan benar
- Jenisnya tidak dapat diubah, sehingga tidak ada kemungkinan untuk bermutasi nilai rekam jepret
Jadi dalam hal ini perilaku default EF Core baik-baik saja apa adanya.
Struktur sederhana yang tidak dapat diubah
Pemetaan untuk struktur sederhana juga sederhana dan tidak memerlukan pembanding atau rekam jepret khusus.
public readonly struct ImmutableStruct
{
public ImmutableStruct(int value)
{
Value = value;
}
public int Value { get; }
}
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyProperty)
.HasConversion(
v => v.Value,
v => new ImmutableStruct(v));
EF Core memiliki dukungan bawaan untuk menghasilkan perbandingan berdasarkan anggota dari properti struct yang dikompilasi. Ini berarti struktur tidak perlu mengambil alih kesetaraan untuk EF Core, tetapi Anda mungkin masih memilih untuk melakukan ini karena alasan lain. Selain itu, rekam jepret khusus tidak diperlukan karena struktur tidak dapat diubah dan selalu disalin secara anggota. (Ini juga berlaku untuk struktur yang dapat diubah, tetapi struktur yang dapat diubah harus dihindari secara umum.)
Kelas yang dapat diubah
Disarankan agar Anda menggunakan jenis yang tidak dapat diubah (kelas atau struktur) dengan pengonversi nilai jika memungkinkan. Ini biasanya lebih efisien dan memiliki semantik yang lebih bersih daripada menggunakan jenis yang dapat diubah. Walaupun demikian, adalah umum untuk menggunakan properti jenis yang tidak dapat diubah oleh aplikasi. Misalnya, memetakan properti yang berisi daftar angka:
public List<int> MyListProperty { get; set; }
Kelas List<T>:
- Memiliki kesetaraan referensi; dua daftar yang berisi nilai yang sama diperlakukan sebagai berbeda.
- Dapat diubah; nilai dalam daftar dapat ditambahkan dan dihapus.
Konversi nilai umum pada properti daftar mungkin mengonversi daftar ke dan dari JSON:
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyListProperty)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
new ValueComparer<List<int>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()));
ValueComparer<T> Konstruktor menerima tiga ekspresi:
- Ekspresi untuk memeriksa kesetaraan
- Ekspresi untuk menghasilkan kode hash
- Ekspresi untuk menangkap nilai
Dalam hal ini perbandingan dilakukan dengan memeriksa apakah urutan angka sama.
Demikian juga, kode hash dibangun dari urutan yang sama ini. (Perhatikan bahwa ini adalah kode hash di atas nilai yang dapat diubah dan karenanya dapat menyebabkan masalah. Jadilah tidak dapat diubah sebagai gantinya jika Anda bisa.)
Cuplikan dibuat dengan menyalin daftar dengan ToList. Sekali lagi, ini hanya diperlukan jika daftar akan dimutasi. Cobalah menjadi tetap jika memungkinkan.
Nota
Pengonversi dan pembanding nilai dibangun menggunakan ekspresi daripada delegat sederhana. Ini karena EF Core menyisipkan ekspresi ini ke dalam pohon ekspresi yang jauh lebih kompleks yang kemudian dikompilasi ke dalam delegasi pembentuk entitas. Secara konseptual, ini mirip dengan proses inlining dalam kompilator. Misalnya, konversi sederhana mungkin hanya dikompilasi dalam pemeran, daripada panggilan ke metode lain untuk melakukan konversi.
Pembanding kunci
Bagian latar belakang mencakup mengapa perbandingan kunci mungkin memerlukan semantik khusus. Pastikan untuk membuat pembanding yang sesuai untuk kunci saat menentukannya pada properti kunci utama, kunci pokok, atau kunci asing.
Gunakan SetKeyValueComparer dalam kasus yang jarang terjadi di mana semantik yang berbeda diperlukan pada properti yang sama.
Nota
SetStructuralValueComparer telah usang. Gunakan SetKeyValueComparer sebagai gantinya.
Mengesampingkan pembanding default
Terkadang perbandingan default yang digunakan oleh EF Core mungkin tidak sesuai. Misalnya, mutasi array byte tidak, secara default, terdeteksi di EF Core. Ini dapat digantikan dengan mengatur pembanding yang berbeda pada properti.
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyBytes)
.Metadata
.SetValueComparer(
new ValueComparer<byte[]>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToArray()));
EF Core sekarang akan membandingkan urutan byte dan oleh karena itu akan mendeteksi mutasi array byte.