¿Cómo puedo consultar valores nulos en el marco de la entidad?

109

Quiero ejecutar una consulta como esta

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

y obtener un IS NULLarchivo.

Editado: Después de las dos primeras respuestas, siento la necesidad de aclarar que estoy usando Entity Framework y no Linq to SQL. El método object.Equals () no parece funcionar en EF.

Edición n. ° 2: la consulta anterior funciona según lo previsto. Genera correctamente IS NULL. Sin embargo, mi código de producción fue

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

y el SQL generado fue something = @p; @p = NULL. Parece que EF traduce correctamente la expresión constante, pero si hay una variable involucrada, la trata como una comparación normal. Tiene sentido en realidad. Cerraré esta pregunta

Adrian Zanescu
fuente
17
Creo que realmente no tiene sentido ... El conector debería ser un poco inteligente y no pedirnos que hagamos su trabajo: realizar una traducción correcta en SQL de la consulta C # correcta. Esto genera un comportamiento inesperado.
Julien N
6
Estoy con Julien, esto es un fracaso por parte de EF
Mr Bell
1
Esto es una falla de los estándares, y solo está empeorando ahora que la comparación con nulo resulta permanentemente en indefinido a partir de SQL Server 2016 con ANSI NULL configurados permanentemente en activado. Nulo puede representar un valor desconocido, pero "nulo" en sí mismo no es un valor desconocido. La comparación de un valor nulo con un valor nulo debería resultar absolutamente verdadero, pero desafortunadamente el estándar se aparta tanto del sentido común como de la lógica booleana.
Triynko

Respuestas:

126

Solución alternativa para Linq-to-SQL:

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

Solución alternativa para Linq-to-Entities (¡ay!):

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

Este es un error desagradable que me ha picado varias veces. Si este error también te ha afectado, visita el informe de errores en UserVoice y hazle saber a Microsoft que este error también te ha afectado.


Editar: ¡ Este error se está solucionando en EF 4.5 ! ¡Gracias a todos por votar a favor de este error!

Para compatibilidad con versiones anteriores, será opt-in: debe habilitar manualmente una configuración para que entry == valuefuncione. Aún no se sabe qué es esta configuración. ¡Manténganse al tanto!


Edición 2: de acuerdo con esta publicación del equipo de EF, ¡ este problema se ha solucionado en EF6! ¡Guau!

Cambiamos el comportamiento predeterminado de EF6 para compensar la lógica de tres valores.

Esto significa que el código existente que se basa en el comportamiento anterior ( null != nullpero solo cuando se compara con una variable) deberá cambiarse para no depender de ese comportamiento, o se UseCSharpNullComparisonBehaviordeberá establecer en falso para usar el comportamiento roto anterior.

BlueRaja - Danny Pflughoeft
fuente
6
He votado a favor del informe de errores. Ojalá solucionen esto. No puedo decir que realmente recuerdo que este error estuvo presente en vs2010 beta ...
noobish
2
oh vamos microsoft ... ¿de verdad?!?!? ¿En la versión 4.1?!?! +1
David
1
Esa solución de Linq-To-SQL no parece funcionar (¿intentarlo con un Guid?). El uso de Entities-Workaround funciona en L2S, pero genera un SQL terrible. Tuve que hacer una declaración if en código(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 funciona en realidad(where Object.Equals(entry.something,value))
Michael Stum
5
@ leen3o (o cualquier otra persona): ¿Alguien ha encontrado todavía dónde está esta supuesta corrección en EF 4.5 / 5.0? Estoy usando 5.0 y todavía se está portando mal.
Shaul Behr
17

Desde Entity Framework 5.0, puede usar el siguiente código para resolver su problema:

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

Esto debería resolver sus problemas ya que Entity Framerwork utilizará una comparación nula 'C # como'.

ITmeze
fuente
16

Hay una solución alternativa un poco más simple que funciona con LINQ to Entities:

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

Esto funciona porque, como observó AZ, LINQ to Entities casos especiales x == null (es decir, una comparación de igualdad contra la constante nula) y lo traduce ax IS NULL.

Actualmente estamos considerando cambiar este comportamiento para introducir las comparaciones de compensación automáticamente si ambos lados de la igualdad son anulables. Sin embargo, hay un par de desafíos:

  1. Esto podría potencialmente romper el código que ya depende del comportamiento existente.
  2. La nueva traducción podría afectar el rendimiento de las consultas existentes incluso cuando rara vez se usa un parámetro nulo.

En cualquier caso, si nos ponemos manos a la obra dependerá en gran medida de la prioridad relativa que le asignen nuestros clientes. Si le preocupa el problema, le animo a que lo vote en nuestro nuevo sitio de Sugerencias de funciones: https://data.uservoice.com .

divega
fuente
9

Si es un tipo que acepta valores NULL, tal vez intente usar la propiedad HasValue.

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

Sin embargo, no tengo ningún EF para probar aquí ... solo una sugerencia =)

Svish
fuente
1
Bueno ... esto solo funciona si solo estás buscando nulos, pero de == nulltodos modos el error no afecta al uso . El punto es filtrar por el valor de una variable, cuyo valor podría ser nulo, y hacer que el valor nulo encuentre los registros nulos.
Dave Cousineau
1
Tu respuesta me salvó. Olvidé usar un tipo que acepta valores NULL en mi clase de modelo de entidad y no pude (x => x.Column == null)trabajar. :)
Reuel Ribeiro
¡Esto da System.NullReferenceException , ya que el objeto ya es nulo!
TiyebM
5

para hacer frente a las comparaciones nulas, utilice en Object.Equals()lugar de==

ver esta referencia

Oscar Cabrero
fuente
Esto funciona perfectamente en Linq-To-Sql y también genera el SQL adecuado (algunas otras respuestas aquí generan un SQL horrendo o resultados incorrectos).
Michael Stum
Supongamos que quiero compaire con null, Object.Equals(null)¿y si la Objectmisma es nula?
TiyebM
4

Señalando que todas las sugerencias de Entity Framework <6.0 generan algo de SQL incómodo. Vea el segundo ejemplo para una solución "limpia".

Solución ridícula

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

resultados en SQL como:

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

Solución indignante

Si desea generar un SQL más limpio, algo como:

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

resultados en lo que quería en primer lugar:

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)
drzaus
fuente
El código que se ejecuta en SQL será más limpio y rápido, pero EF generará y almacenará en caché un nuevo plan de consulta para cada combinación antes de enviarlo al servidor SQL, lo que lo hace más lento que otras soluciones.
Burak Tamtürk
2
var result = from entry in table
                     where entry.something == null
                     select entry;

La consulta anterior funciona según lo previsto. Genera correctamente IS NULL. Sin embargo, mi código de producción fue

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

y el SQL generado fue algo = @p; @p = NULO. Parece que EF traduce correctamente la expresión constante, pero si hay una variable involucrada, la trata como una comparación normal. Tiene sentido en realidad.

Adrian Zanescu
fuente
1

Parece que Linq2Sql también tiene este "problema". Parece que hay una razón válida para este comportamiento debido a si los ANSI NULL están ENCENDIDOS o APAGADOS, pero desconcierta por qué un "== nulo" directo funcionará de hecho como se esperaba.

JasonCoder
fuente
1

Personalmente, prefiero:

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

encima

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

porque evita la repetición, aunque eso no es matemáticamente exacto, pero se adapta bien a la mayoría de los casos.

Vincent Courcelle
fuente
0

No puedo comentar la publicación de divega, pero entre las diferentes soluciones presentadas aquí, la solución de divega produce el mejor SQL. Tanto en cuanto al rendimiento como a lo largo. Acabo de verificar con SQL Server Profiler y mirando el plan de ejecución (con "SET STATISTICS PROFILE ON").

Buginator
fuente
0

Desafortunadamente, en Entity Framework 5 DbContext, el problema aún no está solucionado.

Usé esta solución alternativa (funciona con MSSQL 2012, pero la configuración ANSI NULLS podría quedar obsoleta en cualquier versión futura de 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();
            }
        }
    }
}

Cabe señalar que es una solución sucia pero que se puede implementar muy rápidamente y funciona para todas las consultas.

Knaģis
fuente
Esto dejará de funcionar inmediatamente una vez que ANSI NULLS se establezca permanentemente en ON en una versión futura de SQL Server, en caso de que la advertencia no sea clara.
Triynko
0

Si prefiere usar la sintaxis del método (lambda) como yo, puede hacer lo mismo como esto:

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;
John Meyer
fuente
-1
var result = from entry in table    
             where entry.something == value||entry.something == null                   
              select entry;

usa eso

Andrés
fuente
5
Eso es MUY incorrecto porque seleccionará todas las entradas donde el valor coincida Y todas las entradas donde algo sea nulo, incluso si solicita un valor.
Michael Stum