Semantik kueri null

Pendahuluan

Database SQL beroperasi pada logika bernilai 3 (true, false, null) saat melakukan perbandingan, dibandingkan dengan logika boolean C#. Saat menerjemahkan kueri LINQ ke SQL, EF Core mencoba mengkompensasi perbedaan dengan memperkenalkan pemeriksaan null tambahan untuk beberapa elemen kueri. Untuk mengilustrasikan hal ini, mari kita tentukan entitas berikut:

public class NullSemanticsEntity
{
    public int Id { get; set; }
    public int Int { get; set; }
    public int? NullableInt { get; set; }
    public string String1 { get; set; }
    public string String2 { get; set; }
}

dan mengajukan beberapa kueri:

var query1 = context.Entities.Where(e => e.Id == e.Int);
var query2 = context.Entities.Where(e => e.Id == e.NullableInt);
var query3 = context.Entities.Where(e => e.Id != e.NullableInt);
var query4 = context.Entities.Where(e => e.String1 == e.String2);
var query5 = context.Entities.Where(e => e.String1 != e.String2);

Dua kueri pertama menghasilkan perbandingan sederhana. Dalam kueri pertama, kedua kolom tidak dapat diubah ke null sehingga pemeriksaan null tidak diperlukan. Dalam kueri kedua, NullableInt dapat berisi null, tetapi Id tidak dapat bernilai null; membandingkan null dengan non-null menghasilkan null, yang akan difilter oleh operasi WHERE. Jadi tidak ada istilah tambahan yang diperlukan juga.

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[Id] = [e].[Int]

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[Id] = [e].[NullableInt]

Kueri ketiga memperkenalkan pengecekan null. NullableInt Ketika null perbandingan Id <> NullableInt menghasilkan null, yang akan difilter oleh operasi WHERE. Namun, dari perspektif logika boolean kasus ini harus dikembalikan sebagai bagian dari hasilnya. Maka dari itu EF Core menambahkan pemeriksaan yang diperlukan untuk memastikannya.

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[Id] <> [e].[NullableInt]) OR [e].[NullableInt] IS NULL

Kueri empat dan lima memperlihatkan pola saat kedua kolom dapat bernilai null. Perlu dicatat bahwa operasi <> menghasilkan kueri yang lebih rumit (dan berpotensi lebih lambat) daripada ==.

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[String1] = [e].[String2]) OR ([e].[String1] IS NULL AND [e].[String2] IS NULL)

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE (([e].[String1] <> [e].[String2]) OR ([e].[String1] IS NULL OR [e].[String2] IS NULL)) AND ([e].[String1] IS NOT NULL OR [e].[String2] IS NOT NULL)

Perlakuan nilai yang dapat bernilai null dalam fungsi

Banyak fungsi dalam SQL hanya dapat mengembalikan null hasil jika beberapa argumennya adalah null. EF Core memanfaatkan ini untuk menghasilkan kueri yang lebih efisien. Kueri di bawah ini mengilustrasikan pengoptimalan:

var query = context.Entities.Where(e => e.String1.Substring(0, e.String2.Length) == null);

SQL yang dihasilkan adalah sebagai berikut (kita tidak perlu mengevaluasi SUBSTRING fungsi karena hanya akan null ketika salah satu argumen ke dalamnya null.):

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[String1] IS NULL OR [e].[String2] IS NULL

Pengoptimalan juga dapat digunakan untuk fungsi yang ditentukan pengguna. Lihat halaman pemetaan fungsi yang ditentukan pengguna untuk detail selengkapnya.

Menulis kueri berkinerja tinggi

  • Membandingkan kolom yang tidak dapat diubah ke null lebih sederhana dan lebih cepat daripada membandingkan kolom yang dapat diubah ke null. Pertimbangkan untuk menandai kolom sebagai tidak dapat bernilai null jika memungkinkan.

  • Memeriksa kesetaraan (==) lebih sederhana dan lebih cepat daripada memeriksa non-kesetaraan (!=), karena kueri tidak perlu membedakan antara null dan false hasil. Gunakan perbandingan kesetaraan jika memungkinkan. Namun, dengan hanya meniadakan == perbandingan secara efektif sama dengan !=, sehingga tidak menghasilkan peningkatan kinerja.

  • Dalam beberapa kasus, dimungkinkan untuk menyederhanakan perbandingan kompleks dengan memfilter null nilai dari kolom secara eksplisit - misalnya ketika tidak ada null nilai yang ada atau nilai-nilai ini tidak relevan dalam hasilnya. Pertimbangkan contoh berikut:

var query1 = context.Entities.Where(e => e.String1 != e.String2 || e.String1.Length == e.String2.Length);
var query2 = context.Entities.Where(
    e => e.String1 != null && e.String2 != null && (e.String1 != e.String2 || e.String1.Length == e.String2.Length));

Kueri ini menghasilkan SQL berikut:

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ((([e].[String1] <> [e].[String2]) OR ([e].[String1] IS NULL OR [e].[String2] IS NULL)) AND ([e].[String1] IS NOT NULL OR [e].[String2] IS NOT NULL)) OR ((CAST(LEN([e].[String1]) AS int) = CAST(LEN([e].[String2]) AS int)) OR ([e].[String1] IS NULL AND [e].[String2] IS NULL))

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[String1] IS NOT NULL AND [e].[String2] IS NOT NULL) AND (([e].[String1] <> [e].[String2]) OR (CAST(LEN([e].[String1]) AS int) = CAST(LEN([e].[String2]) AS int)))

Pada kueri kedua, hasil null difilter secara eksplisit dari kolom String1. EF Core dapat dengan aman memperlakukan kolom String1 sebagai tidak dapat bernilai null selama perbandingan, menghasilkan kueri yang lebih sederhana.

Menggunakan semantik null relasional

Dimungkinkan untuk menonaktifkan kompensasi perbandingan null dan menggunakan semantik null relasional secara langsung. Ini dapat dilakukan dengan memanggil UseRelationalNulls(true) metode pada pembangun opsi di dalam OnConfiguring metode:

new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();

Peringatan

Saat menggunakan semantik null relasional, kueri LINQ Anda tidak lagi memiliki arti yang sama seperti yang mereka lakukan di C#, dan dapat menghasilkan hasil yang berbeda dari yang diharapkan. Berhati-hatilah saat menggunakan mode ini.