Bagikan melalui


Semantik null kueri

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 mengeluarkan 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 diubah ke null; dibandingkan null dengan hasil non-null null sebagai hasilnya, yang akan difilter berdasarkan WHERE operasi. 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 pemeriksaan null. null Kapan NullableInt perbandingan Id <> NullableInt menghasilkan null, yang akan difilter berdasarkan WHERE operasi. Namun, dari perspektif logika boolean kasus ini harus dikembalikan sebagai bagian dari hasilnya. Oleh karena itu EF Core menambahkan pemeriksaan yang diperlukan untuk memastikan bahwa.

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 diubah ke null. Perlu dicatat bahwa operasi menghasilkan kueri yang <> lebih rumit (dan berpotensi lebih lambat) daripada == operasi.

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

  • 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 diubah ke 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, hanya meniadakan == perbandingan secara efektif sama !=dengan , sehingga tidak menghasilkan peningkatan performa.

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

Di kueri kedua, null hasil difilter dari String1 kolom secara eksplisit. EF Core dapat dengan aman memperlakukan String1 kolom sebagai tidak dapat diubah ke 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.