Come posso eseguire una query per valori null in entity framework?


109

Voglio eseguire una query come questa

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

e ottieni un file IS NULL.

Modificato: dopo le prime due risposte sento il bisogno di chiarire che sto utilizzando Entity Framework e non Linq to SQL. Il metodo object.Equals () non sembra funzionare in EF.

Modifica n. 2: la query precedente funziona come previsto. Genera correttamente IS NULL. Tuttavia, il mio codice di produzione era

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

e l'SQL generato era something = @p; @p = NULL. Sembra che EF traduca correttamente l'espressione costante ma se è coinvolta una variabile la tratta proprio come un normale confronto. Ha senso in realtà. Chiudo questa domanda


17
Penso che non abbia davvero senso ... Il connettore dovrebbe essere un po 'intelligente e non chiederci di fare il suo lavoro: eseguire una traduzione corretta in SQL della query C # corretta. Questo genera un comportamento imprevisto.
Julien N

6
Sono con Julien, questo è un fallimento da parte di EF
Mr Bell

1
Questo è un fallimento degli standard e sta peggiorando ora che il confronto con null risulta permanentemente indefinito a partire da SQL Server 2016 con NULL ANSI impostati permanentemente su on. Null può rappresentare un valore sconosciuto, ma "null" stesso non è un valore sconosciuto. Il confronto di un valore nullo con un valore nullo dovrebbe assolutamente restituire vero, ma sfortunatamente lo standard si discosta dal buon senso e dalla logica booleana.
Triynko

Risposte:


126

Soluzione alternativa per Linq-to-SQL:

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

Soluzione alternativa per Linq-to-Entities (ahi!):

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

Questo è un brutto bug che mi ha morso più volte. Se questo bug ha colpito anche te, visita la segnalazione di bug su UserVoice e fai sapere a Microsoft che questo bug ha colpito anche te.


Modifica: questo bug viene corretto in EF 4.5 ! Grazie a tutti per aver votato positivamente questo bug!

Per la compatibilità con le versioni precedenti, sarà opt-in: è necessario abilitare manualmente un'impostazione per entry == valuefunzionare. Non si sa ancora cosa sia questa impostazione. Rimanete sintonizzati!


Modifica 2: secondo questo post del team EF, questo problema è stato risolto in EF6! Woohoo!

Abbiamo modificato il comportamento predefinito di EF6 per compensare la logica a tre valori.

Ciò significa che il codice esistente che si basa sul vecchio comportamento ( null != nullma solo quando viene confrontato con una variabile) dovrà essere modificato per non fare affidamento su quel comportamento o impostato UseCSharpNullComparisonBehaviorsu false per utilizzare il vecchio comportamento non funzionante.


6
Ho votato a favore della segnalazione di bug. Si spera che risolvano questo problema. Non posso dire di ricordare davvero che questo bug fosse presente nella versione beta di vs2010 ...
noobish

2
oh andiamo microsoft ... davvero?!?!? Nella versione 4.1?!?! +1
David

1
Quella soluzione alternativa Linq-To-SQL non sembra funzionare (provando con un Guid?). L'utilizzo di Entities-Workaround funziona in L2S, ma genera un orrendo SQL. Ho dovuto fare un'istruzione if in codice(var result = from ...; if(value.HasValue) result = result.Where(e => e.something == value) else result = result.Where(e => e.something == null);
Michael Stum

5
Object.Equals funziona davvero(where Object.Equals(entry.something,value))
Michael Stum

5
@ leen3o (o chiunque altro) - Qualcuno ha ancora trovato dove si trova questa presunta correzione in EF 4.5 / 5.0? Sto usando 5.0 e continua a comportarsi male.
Shaul Behr

17

A partire da Entity Framework 5.0 è possibile utilizzare il seguente codice per risolvere il problema:

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

Questo dovrebbe risolvere i tuoi problemi poiché Entity Framerwork userà il confronto null 'C # like'.


16

Esiste una soluzione alternativa leggermente più semplice che funziona con LINQ to Entities:

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

Questo funziona perché, come ha notato AZ, LINQ to Entities casi speciali x == null (cioè un confronto di uguaglianza con la costante nulla) e lo traduce in x IS NULL.

Stiamo attualmente valutando la possibilità di modificare questo comportamento per introdurre automaticamente i confronti compensativi se entrambi i lati dell'uguaglianza sono annullabili. Ci sono però un paio di sfide:

  1. Ciò potrebbe potenzialmente interrompere il codice che dipende già dal comportamento esistente.
  2. La nuova traduzione potrebbe influire sulle prestazioni delle query esistenti anche quando un parametro null viene utilizzato raramente.

In ogni caso, il fatto che ci si metta al lavoro dipenderà molto dalla priorità relativa che i nostri clienti gli assegnano. Se ti interessa il problema, ti incoraggio a votarlo nel nostro nuovo sito di suggerimenti sulle funzionalità: https://data.uservoice.com .


9

Se è un tipo nullable, forse provare a utilizzare la proprietà HasValue?

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

Non ho alcun EF su cui testare qui però ... solo un suggerimento =)


1
Bene ... questo funziona solo se stai solo cercando dei null, ma poi l'uso == nullnon viene comunque colpito dal bug. Il punto è filtrare in base al valore di una variabile, il cui valore potrebbe essere nullo, e fare in modo che il valore nullo trovi i record nulli.
Dave Cousineau

1
La tua risposta mi ha salvato. Ho dimenticato di utilizzare un tipo nullable sulla mia classe del modello di entità e non sono riuscito a farlo (x => x.Column == null)funzionare. :)
Reuel Ribeiro

Questo dà System.NullReferenceException , poiché l'oggetto già è nullo!
TiyebM


5

per gestire i confronti nulli utilizzare Object.Equals()invece di==

controllare questo riferimento


Funziona perfettamente in Linq-To-Sql e genera anche l'SQL corretto (alcune altre risposte qui generano SQL orrendo o risultati errati).
Michael Stum

Supponiamo che io voglio compaire con null, Object.Equals(null), cosa succede se la Objectstessa è nulla?
TiyebM

4

Sottolineando che tutti i suggerimenti di Entity Framework <6.0 generano un SQL imbarazzante. Vedere il secondo esempio per la correzione "pulita".

Soluzione alternativa ridicola

// 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
    );

risultati in SQL come:

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

Soluzione oltraggiosa

Se vuoi generare un SQL più pulito, qualcosa come:

// 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()
    ;

risultati in quello che volevi in ​​primo luogo:

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)

Il codice in esecuzione su SQL sarà più pulito e veloce ma EF genererà e memorizzerà nella cache un nuovo piano di query per ogni combinazione prima di inviarlo al server sql, il che lo rende più lento di altre soluzioni alternative.
Burak Tamtürk

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

La query precedente funziona come previsto. Genera correttamente IS NULL. Tuttavia, il mio codice di produzione era

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

e l'SQL generato era qualcosa = @p; @p = NULL. Sembra che EF traduca correttamente l'espressione costante ma se è coinvolta una variabile la tratta proprio come un normale confronto. Ha senso in realtà.


1

Sembra che anche Linq2Sql abbia questo "problema". Sembra che ci sia una ragione valida per questo comportamento a causa del fatto che i NULL ANSI siano ON o OFF, ma lascia perplessi il motivo per cui un "== null" diretto funzionerà come ti aspetteresti.


1

Personalmente preferisco:

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

al di sopra di

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

perché impedisce la ripetizione, sebbene non sia matematicamente esatto, ma si adatta bene alla maggior parte dei casi.


0

Non sono in grado di commentare il post di divega, ma tra le diverse soluzioni qui presentate, quella di divega produce il miglior SQL. Sia per quanto riguarda le prestazioni che per la lunghezza. Ho appena controllato con SQL Server Profiler e guardando il piano di esecuzione (con "SET STATISTICS PROFILE ON").


0

Sfortunatamente in Entity Framework 5 DbContext il problema non è stato ancora risolto.

Ho usato questa soluzione alternativa (funziona con MSSQL 2012 ma l'impostazione ANSI NULLS potrebbe essere deprecata in qualsiasi versione futura di MSSQL).

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();
            }
        }
    }
}

Va notato che è una soluzione alternativa sporca, ma può essere implementata molto rapidamente e funziona per tutte le query.


Ciò cesserà immediatamente di funzionare una volta che ANSI NULLS sarà impostato permanentemente su ON in una versione futura di SQL Server, nel caso in cui l'avviso non fosse chiaro.
Triynko

0

Se preferisci usare la sintassi del metodo (lambda) come me, potresti fare la stessa cosa in questo modo:

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;

usa quello


5
È MOLTO sbagliato perché selezionerà tutte le voci in cui il valore corrisponde E tutte le voci in cui qualcosa è nullo, anche se chiedi un valore.
Michael Stum
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.