Varlık çerçevesindeki boş değerleri nasıl sorgulayabilirim?


109

Bunun gibi bir sorgu yürütmek istiyorum

   var result = from entry in table
                     where entry.something == null
                     select entry;

ve IS NULLoluşturulmuş bir.

Düzenlendi: İlk iki cevaptan sonra Linq to SQL değil Entity Framework kullandığımı netleştirme ihtiyacı duyuyorum. Object.Equals () yöntemi EF'de çalışmıyor gibi görünüyor.

Düzenleme 2: Yukarıdaki sorgu amaçlandığı gibi çalışır. Doğru şekilde üretir IS NULL. Ancak üretim kodum

value = null;
var result = from entry in table
                         where entry.something == value
                         select entry;

ve oluşturulan SQL oldu something = @p; @p = NULL. Görünüşe göre EF sabit ifadeyi doğru bir şekilde çeviriyor, ancak bir değişken varsa, onu normal bir karşılaştırma gibi ele alıyor. Aslında mantıklı. Bu soruyu kapatacağım


17
Bunun gerçekten mantıklı olmadığını düşünüyorum ... Bağlayıcı biraz akıllı olmalı ve bizden işini yapmamızı istememelidir: doğru C # sorgusunun SQL'de doğru bir çevirisini gerçekleştirin. Bu, beklenmeyen bir davranış oluşturur.
Julien N

6
Julien'le birlikteyim, bu EF açısından bir başarısızlık
Bay Bell

1
Bu, standartların bir başarısızlığıdır ve artık daha da kötüye gitmektedir; null ile karşılaştırmanın kalıcı olarak ANSI NULL'ların kalıcı olarak açık olduğu SQL Server 2016'dan itibaren tanımsız olarak sonuçlanması. Null bilinmeyen bir değeri temsil edebilir , ancak "null" bilinmeyen bir değer değildir. Boş değer ile boş değerin karşılaştırılması kesinlikle doğru sonucunu vermelidir, ancak ne yazık ki standart hem sağduyu hem de Boole mantığından ayrılmaktadır.
Triynko

Yanıtlar:


126

Linq-to-SQL için geçici çözüm:

var result = from entry in table
             where entry.something.Equals(value)
             select entry;

Linq-to-Entities (ouch!) İçin geçici çözüm:

var result = from entry in table
             where (value == null ? entry.something == null : entry.something == value)
             select entry;

Bu beni birkaç kez ısıran iğrenç bir böcek. Bu hata sizi de etkilediyse, lütfen UserVoice üzerindeki hata raporunu ziyaret edin ve Microsoft'a bu hatanın sizi de etkilediğini bildirin.


Düzenleme: Bu hata EF 4.5'te düzeltilmektedir ! Bu hatayı desteklediğiniz için herkese teşekkürler!

Geriye dönük uyumluluk için, kabul edilecektir - entry == valueçalışması için bir ayarı manuel olarak etkinleştirmeniz gerekir . Bu ayarın ne olduğu hakkında henüz bir bilgi yok. Bizi izlemeye devam edin!


Düzenleme 2: göre bu yazı EF ekibi tarafından, bu konunun Ef6 sabit olmuştur! Bravo!

Üç değerli mantığı telafi etmek için EF6'nın varsayılan davranışını değiştirdik.

Bu, eski davranışa dayanan ( null != nullancak yalnızca bir değişkenle karşılaştırılırken) mevcut kodun bu davranışa dayanmayacak şekilde değiştirilmesi veya UseCSharpNullComparisonBehavioreski bozuk davranışı kullanmak için yanlış olarak ayarlanması gerektiği anlamına gelir.


6
Hata raporunu oyladım. Umarım bunu düzeltirler. Bu hatanın vs2010 beta sürümünde mevcut olduğunu gerçekten hatırladığımı söyleyemem ...
noobish

2
oh microsoft'a gel ... gerçekten mi?!?!? 4.1 sürümünde?!?! +1
David

1
Linq-To-SQL geçici çözümü işe yaramıyor gibi görünüyor (bir Guid ile mi çalışıyorsunuz?). Varlıklar-Geçici Çözümün kullanılması L2S'de çalışır, ancak korkunç SQL üretir. (var result = from ...; if(value.HasValue) result = result.Where(e => e.something == value) else result = result.Where(e => e.something == null);
Kodla

5
Object.Equals gerçekten işe yarıyor(where Object.Equals(entry.something,value))
Michael Stum

5
@ leen3o (veya başka biri) - Bu iddia edilen düzeltmenin EF 4.5 / 5.0'da nerede olduğunu henüz bulan oldu mu? 5.0 kullanıyorum ve hala yanlış davranıyor.
Shaul Behr

17

Entity Framework 5.0'dan bu yana, sorununuzu çözmek için aşağıdaki kodu kullanabilirsiniz:

public abstract class YourContext : DbContext
{
  public YourContext()
  {
    (this as IObjectContextAdapter).ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior = true;
  }
}

Entity Framerwork 'C # like' null karşılaştırmasını kullanacağından, bu problemlerinizi çözmelidir.


16

LINQ to Entities ile çalışan biraz daha basit bir çözüm vardır:

var result = from entry in table
         where entry.something == value || (value == null && entry.something == null)
         select entry;

Bu, AZ'nin de fark ettiği gibi, LINQ to Entities özel durumları x == null (yani boş sabite karşı eşitlik karşılaştırması) ve bunu x IS NULL'a çevirdiği için çalışır.

Eşitliğin her iki tarafı da null atanabilirse, telafi edici karşılaştırmaları otomatik olarak sunmak için şu anda bu davranışı değiştirmeyi düşünüyoruz. Yine de birkaç zorluk var:

  1. Bu, zaten mevcut davranışa bağlı olan kodu bozabilir.
  2. Yeni çeviri, boş bir parametre nadiren kullanılsa bile mevcut sorguların performansını etkileyebilir.

Her halükarda, bunun üzerinde çalışıp çalışmayacağımız, büyük ölçüde müşterilerimizin ona atadığı göreceli önceliğe bağlı olacaktır. Sorunla ilgileniyorsanız, yeni Özellik Önerisi sitemiz olan https://data.uservoice.com üzerinden oy vermenizi tavsiye ederim .


9

Boş değer atanabilir bir türse, HasValue özelliğini kullanmayı deneyebilirsiniz.

var result = from entry in table
                 where !entry.something.HasValue
                 select entry;

Burada test etmek için herhangi bir EF yok ... sadece bir öneri =)


1
Şey ... bu sadece boşlar arıyorsanız işe yarar, ancak o zaman kullanmak == nullzaten böcek tarafından vurulmuyor. Önemli olan, değeri null olabilecek bir değişkenin değerine göre filtreleme yapmak ve boş değeri olan boş kayıtları bulmaktır.
Dave Cousineau

1
Cevabınız beni kurtardı. Varlık modeli sınıfımda boş değer atanabilir bir tür kullanmayı unuttum ve (x => x.Column == null)işe yaramadım. :)
Reuel Ribeiro

Bu verir System.NullReferenceException , çünkü nesne zaten boştur!
TiyebM


5

Boş Karşılaştırmalarla uğraşmak için Object.Equals()yerine kullanın==

bu referansı kontrol et


Bu, Linq-To-Sql'de mükemmel çalışır ve ayrıca uygun SQL'i oluşturur (buradaki bazı diğer cevaplar korkunç SQL veya yanlış sonuçlar üretir).
Michael Stum

Birlikte compaire istediğinizi varsayalım null, Object.Equals(null)ne olursa Objectkendisi null?
TiyebM

4

Tüm Entity Framework <6.0 önerilerinin bazı garip SQL oluşturduğuna işaret ederek. "Temiz" düzeltme için ikinci örneğe bakın.

Gülünç Geçici Çözüm

// comparing against this...
Foo item = ...

return DataModel.Foos.FirstOrDefault(o =>
    o.ProductID == item.ProductID
    // ridiculous < EF 4.5 nullable comparison workaround http://stackoverflow.com/a/2541042/1037948
    && item.ProductStyleID.HasValue ? o.ProductStyleID == item.ProductStyleID : o.ProductStyleID == null
    && item.MountingID.HasValue ? o.MountingID == item.MountingID : o.MountingID == null
    && item.FrameID.HasValue ? o.FrameID == item.FrameID : o.FrameID == null
    && o.Width == w
    && o.Height == h
    );

aşağıdaki gibi SQL ile sonuçlanır:

SELECT TOP (1) [Extent1].[ID]                 AS [ID],
       [Extent1].[Name]               AS [Name],
       [Extent1].[DisplayName]        AS [DisplayName],
       [Extent1].[ProductID]          AS [ProductID],
       [Extent1].[ProductStyleID]     AS [ProductStyleID],
       [Extent1].[MountingID]         AS [MountingID],
       [Extent1].[Width]              AS [Width],
       [Extent1].[Height]             AS [Height],
       [Extent1].[FrameID]            AS [FrameID],
FROM   [dbo].[Foos] AS [Extent1]
WHERE  (CASE
  WHEN (([Extent1].[ProductID] = 1 /* @p__linq__0 */)
        AND (NULL /* @p__linq__1 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[ProductStyleID] = NULL /* @p__linq__2 */) THEN cast(1 as bit)
      WHEN ([Extent1].[ProductStyleID] <> NULL /* @p__linq__2 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[ProductStyleID] IS NULL)
        AND (2 /* @p__linq__3 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[MountingID] = 2 /* @p__linq__4 */) THEN cast(1 as bit)
      WHEN ([Extent1].[MountingID] <> 2 /* @p__linq__4 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[MountingID] IS NULL)
        AND (NULL /* @p__linq__5 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[FrameID] = NULL /* @p__linq__6 */) THEN cast(1 as bit)
      WHEN ([Extent1].[FrameID] <> NULL /* @p__linq__6 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[FrameID] IS NULL)
        AND ([Extent1].[Width] = 20 /* @p__linq__7 */)
        AND ([Extent1].[Height] = 16 /* @p__linq__8 */)) THEN cast(1 as bit)
  WHEN (NOT (([Extent1].[FrameID] IS NULL)
             AND ([Extent1].[Width] = 20 /* @p__linq__7 */)
             AND ([Extent1].[Height] = 16 /* @p__linq__8 */))) THEN cast(0 as bit)
END) = 1

Çirkin Geçici Çözüm

Daha temiz bir SQL oluşturmak istiyorsanız, şunun gibi bir şey:

// outrageous < EF 4.5 nullable comparison workaround http://stackoverflow.com/a/2541042/1037948
Expression<Func<Foo, bool>> filterProductStyle, filterMounting, filterFrame;
if(item.ProductStyleID.HasValue) filterProductStyle = o => o.ProductStyleID == item.ProductStyleID;
else filterProductStyle = o => o.ProductStyleID == null;

if (item.MountingID.HasValue) filterMounting = o => o.MountingID == item.MountingID;
else filterMounting = o => o.MountingID == null;

if (item.FrameID.HasValue) filterFrame = o => o.FrameID == item.FrameID;
else filterFrame = o => o.FrameID == null;

return DataModel.Foos.Where(o =>
    o.ProductID == item.ProductID
    && o.Width == w
    && o.Height == h
    )
    // continue the outrageous workaround for proper sql
    .Where(filterProductStyle)
    .Where(filterMounting)
    .Where(filterFrame)
    .FirstOrDefault()
    ;

ilk etapta istediğiniz şeyi verir:

SELECT TOP (1) [Extent1].[ID]                 AS [ID],
           [Extent1].[Name]               AS [Name],
           [Extent1].[DisplayName]        AS [DisplayName],
           [Extent1].[ProductID]          AS [ProductID],
           [Extent1].[ProductStyleID]     AS [ProductStyleID],
           [Extent1].[MountingID]         AS [MountingID],
           [Extent1].[Width]              AS [Width],
           [Extent1].[Height]             AS [Height],
           [Extent1].[FrameID]            AS [FrameID],
FROM   [dbo].[Foos] AS [Extent1]
WHERE  ([Extent1].[ProductID] = 1 /* @p__linq__0 */)
   AND ([Extent1].[Width] = 16 /* @p__linq__1 */)
   AND ([Extent1].[Height] = 20 /* @p__linq__2 */)
   AND ([Extent1].[ProductStyleID] IS NULL)
   AND ([Extent1].[MountingID] = 2 /* @p__linq__3 */)
   AND ([Extent1].[FrameID] IS NULL)

SQL'de çalışan kod daha temiz ve daha hızlı olacaktır, ancak EF, her kombinasyon için yeni sorgu planını oluşturacak ve önbelleğe alacaktır, bu da onu sql sunucusuna göndermeden önce diğer geçici çözümlerden daha yavaş hale getirir.
Burak Tamtürk

2
var result = from entry in table
                     where entry.something == null
                     select entry;

Yukarıdaki sorgu amaçlandığı gibi çalışır. Doğru şekilde IS NULL üretir. Ancak üretim kodum

var value = null;
var result = from entry in table
                         where entry.something == value
                         select entry;

ve oluşturulan SQL = @p; @p = NULL. Görünüşe göre EF sabit ifadeyi doğru bir şekilde çeviriyor, ancak bir değişken varsa, onu normal bir karşılaştırma gibi ele alıyor. Aslında mantıklı.


1

Görünüşe göre Linq2Sql'de bu "problem" var. ANSI NULL'ların AÇIK veya KAPALI olmasına bağlı olarak bu davranışın geçerli bir nedeni var gibi görünüyor, ancak düz bir "== null" aslında beklediğiniz gibi çalışacak.


1

Şahsen tercih ederim:

var result = from entry in table    
             where (entry.something??0)==(value??0)                    
              select entry;

bitmiş

var result = from entry in table
             where (value == null ? entry.something == null : entry.something == value)
             select entry;

çünkü yinelemeyi önlüyor - bu matematiksel olarak kesin olmasa da çoğu duruma iyi uyuyor.


0

Divega'nın gönderisine yorum yapamıyorum, ancak burada sunulan farklı çözümler arasında divega'nın çözümü en iyi SQL'i üretiyor. Hem performans hem de uzunluk açısından. Sadece SQL Server Profiler ile ve yürütme planına bakarak ("SET STATISTICS PROFILE ON" ile) kontrol ettim.


0

Maalesef Entity Framework 5 DbContext'te sorun hala düzeltilmemiştir.

Bu geçici çözümü kullandım (MSSQL 2012 ile çalışır, ancak ANSI NULLS ayarı gelecekteki herhangi bir MSSQL sürümünde kullanımdan kaldırılabilir).

public class Context : DbContext
{

    public Context()
        : base("name=Context")
    {
        this.Database.Connection.StateChange += Connection_StateChange;
    }

    void Connection_StateChange(object sender, System.Data.StateChangeEventArgs e)
    {
        // Set ANSI_NULLS OFF when any connection is opened. This is needed because of a bug in Entity Framework
        // that is not fixed in EF 5 when using DbContext.
        if (e.CurrentState == System.Data.ConnectionState.Open)
        {
            var connection = (System.Data.Common.DbConnection)sender;
            using (var cmd = connection.CreateCommand())
            {
                cmd.CommandText = "SET ANSI_NULLS OFF";
                cmd.ExecuteNonQuery();
            }
        }
    }
}

Kirli bir çözüm olduğu unutulmamalıdır, ancak çok hızlı bir şekilde uygulanabilen ve tüm sorgular için işe yarayan bir çözümdür.


Bu, uyarının net olmaması durumunda, SQL Server'ın gelecekteki bir sürümünde ANSI NULLS kalıcı olarak AÇIK olarak ayarlandığında işlevini hemen durduracaktır.
Triynko

0

Benim yaptığım gibi yöntem (lambda) sözdizimini kullanmayı tercih ederseniz, aynı şeyi şu şekilde yapabilirsiniz:

var result = new TableName();

using(var db = new EFObjectContext)
{
    var query = db.TableName;

    query = value1 == null 
        ? query.Where(tbl => tbl.entry1 == null) 
        : query.Where(tbl => tbl.entry1 == value1);

    query = value2 == null 
        ? query.Where(tbl => tbl.entry2 == null) 
        : query.Where(tbl => tbl.entry2 == value2);

    result = query
        .Select(tbl => tbl)
        .FirstOrDefault();

   // Inspect the value of the trace variable below to see the sql generated by EF
   var trace = ((ObjectQuery<REF_EQUIPMENT>) query).ToTraceString();

}

return result;

-1
var result = from entry in table    
             where entry.something == value||entry.something == null                   
              select entry;

bunu kullan


5
Bu ÇOK yanlış çünkü değerin eşleştiği tüm girişleri VE bir değer isteseniz bile bir şeyin boş olduğu tüm girişleri seçecektir.
Michael Stum
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.