Aracılığıyla paylaş


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 NullableIntnull, 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 nullolduğunda NullableIntnull.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 nullbazı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 birbirinden nullfalse 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ığında null 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 nullString1 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.