Sorgu null semantiği
Giriş
SQL veritabanları, C# boole mantığının aksine karşılaştırmalar gerçekleştirirken 3 değerli mantık (true
, false
, null
) üzerinde çalışır. EF Core, LINQ sorgularını SQL'e çevirirken, sorgunun bazı öğeleri için ek null denetimleri ekleyerek farkı telafi etmeye çalışır.
Bunu göstermek için aşağıdaki varlığı tanımlayalım:
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; }
}
ve birkaç sorgu oluşturun:
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);
İlk iki sorgu basit karşılaştırmalar oluşturur. İlk sorguda, her iki sütun da null değer atanamaz, bu nedenle null denetimler gerekli değildir. İkinci sorguda içerebilir NullableInt
null
, ancak Id
null değer atanamaz; sonuç olarak null olmayan verimlerle null
karşılaştırılarak null
işlem tarafından WHERE
filtrelenebilir. Bu nedenle ek koşullar da gerekli değildir.
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]
Üçüncü sorgu null denetime neden oldu. Karşılaştırma, işlemine göre filtrelenecek WHERE
olan verimleri null
olduğunda NullableInt
null
.Id <> NullableInt
Ancak boole mantığı açısından bu durum sonucun bir parçası olarak döndürülmelidir. Bu nedenle EF Core, bunu sağlamak için gerekli denetimi ekler.
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
Dört ve beş sorgular, her iki sütun da null atanabilir olduğunda deseni gösterir. İşlemin <>
, işlemden daha karmaşık (ve potansiyel olarak daha yavaş) sorgu ürettiğine ==
dikkat edin.
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)
İşlevlerdeki null atanabilir değerlerin işlenmesi
SQL'deki birçok işlev yalnızca bağımsız değişkenlerinden null
bazıları ise sonuç null
döndürebilir. EF Core, daha verimli sorgular üretmek için bundan yararlanır.
Aşağıdaki sorgu iyileştirmeyi gösterir:
var query = context.Entities.Where(e => e.String1.Substring(0, e.String2.Length) == null);
Oluşturulan SQL aşağıdaki gibidir (işlevi değerlendirmemiz SUBSTRING
gerekmez çünkü bağımsız değişkenlerden biri null olduğunda yalnızca null olur.):
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
İyileştirme, kullanıcı tanımlı işlevler için de kullanılabilir. Daha fazla ayrıntı için kullanıcı tanımlı işlev eşleme sayfasına bakın.
Performans gösteren sorgular yazma
Null değer atanamayan sütunları karşılaştırmak, null atanabilir sütunları karşılaştırmaktan daha basit ve daha hızlıdır. Mümkün olduğunda sütunları null atanamaz olarak işaretlemeyi göz önünde bulundurun.
Eşitlik (
==
) denetimi, eşitlik dışı!=
( denetiminden daha basit ve hızlıdır), çünkü sorgu ile sonucu birbirindennull
false
ayırt etmek zorunda değildir. Mümkün olduğunda eşitlik karşılaştırması kullanın. Ancak karşılaştırmanın olumsuzlaştırılması==
ile etkili bir şekilde aynıdır!=
, bu nedenle performans artışına neden olmaz.Bazı durumlarda, bir sütundaki değerleri açıkça filtreleyerek
null
karmaşık bir karşılaştırmayı basitleştirmek mümkündür; örneğin, değer olmadığındanull
veya bu değerler sonuçla ilgili olmadığında. Aşağıdaki örneği göz önünde bulundurun:
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));
Bu sorgular aşağıdaki SQL'i oluşturur:
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)))
İkinci sorguda sonuçlar sütundan null
String1
açıkça filtrelenir. EF Core, karşılaştırma sırasında sütunu null değer atanamaz olarak güvenli bir şekilde değerlendirerek String1
daha basit bir sorgu elde edebilir.
İlişkisel null semantiği kullanma
Null karşılaştırma telafisini devre dışı bırakmak ve ilişkisel null semantiği doğrudan kullanmak mümkündür. Bu işlem, yöntemin içindeki OnConfiguring
seçenekler oluşturucusunun yöntemini çağırarak UseRelationalNulls(true)
yapılabilir:
new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();
Uyarı
İlişkisel null semantiği kullanırken, LINQ sorgularınızın artık C# ile aynı anlamı yoktur ve beklenenden farklı sonuçlar verebilir. Bu modu kullanırken dikkatli olun.