Como posso consultar valores nulos na estrutura de entidade?

Eu quero executar uma consulta como esta

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

e obter um IS NULL gerado.

Editado: Após as duas primeiras respostas, sinto a necessidade de esclarecer que estou usando o Entity Framework e não o Linq to SQL. O método object.Equals () parece não funcionar no EF.

Editar no.2: A consulta acima funciona conforme pretendido. Ele gera corretamente IS NULL . Meu código de produção, no entanto, foi

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

e o SQL gerado era something = @p; @p = NULL something = @p; @p = NULL Parece que a EF traduz corretamente a expressão constante, mas se uma variável está envolvida, ela a trata como uma comparação normal. Faz sentido, na verdade. Vou fechar essa questão

Solução alternativa para o Linq-to-SQL:

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

Solução alternativa para o Linq-to-Entities (ai!):

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

Este é um erro desagradável que me mordeu várias vezes. Se esse bug também afetou você, visite o relatório de bug no UserVoice e informe a Microsoft que esse bug também afetou você.


Edit: Este bug está sendo corrigido no EF 4.5 ! Obrigado a todos pelo upvoting este bug!

Para compatibilidade com versões anteriores, ele será opt-in – você precisa habilitar manualmente uma configuração para tornar o trabalho de entry == value . Nenhuma palavra ainda sobre o que é essa configuração. Fique ligado!


Edit 2: De acordo com este post da equipe da EF, este problema foi corrigido no EF6! Woohoo!

Alteramos o comportamento padrão do EF6 para compensar a lógica de três valores.

Isso significa que o código existente que se baseia no comportamento antigo ( null != null , mas somente ao comparar com uma variável) precisará ser alterado para não depender desse comportamento ou definir UseCSharpNullComparisonBehavior como false para usar o antigo comportamento quebrado.

Desde o Entity Framework 5.0, você pode usar o seguinte código para resolver seu problema:

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

Isso deve resolver seus problemas, pois o Entity Framerwork usará a comparação nula ‘C # like’.

Existe uma solução um pouco mais simples que funciona com o LINQ to Entities:

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

Isso funciona porque, como observou AZ, os casos especiais LINQ to Entities x == null (ou seja, uma comparação de igualdade em relação à constante nula) e traduz para x IS NULL.

No momento, estamos considerando alterar esse comportamento para introduzir as comparações de compensação automaticamente, se ambos os lados da igualdade forem anuláveis. Há alguns desafios, no entanto:

  1. Isso poderia potencialmente quebrar o código que já depende do comportamento existente.
  2. A nova tradução pode afetar o desempenho de consultas existentes, mesmo quando um parâmetro nulo é raramente usado.

Em qualquer caso, se trabalharmos nisso, isso dependerá muito da prioridade relativa que nossos clientes atribuem a ela. Se você se preocupa com o problema, incentivo você a votar em nosso novo site de sugestões de resources: https://data.uservoice.com .

Se for um tipo anulável, talvez tente usar a propriedade HasValue?

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

Não tem nenhum EF para testar aqui … apenas uma sugestão =)

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

Referência MSDN : LINQ para SQL: consulta integrada à linguagem .NET para dados relacionais

para lidar com comparações nulas use Object.Equals() vez de ==

verifique esta referência

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

A consulta acima funciona conforme pretendido. Ele gera corretamente IS NULL. Meu código de produção, no entanto, foi

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

e o SQL gerado era algo = @p; @p = NULL Parece que a EF traduz corretamente a expressão constante, mas se uma variável está envolvida, ela a trata como uma comparação normal. Faz sentido, na verdade.

Apontando que todas as sugestões do Entity Framework <6.0 geram alguns SQLs desajeitados. Veja o segundo exemplo para correção "limpa".

Solução alternativa 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 ); 

resulta em 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 

Solução alternativa ultrajante

Se você quiser gerar um SQL mais limpo, algo como:

 // outrageous < EF 4.5 nullable comparison workaround http://stackoverflow.com/a/2541042/1037948 Expression> 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() ; 

resulta no que você queria em primeiro 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) 

Parece que o Linq2Sql também tem esse “problema”. Parece que há uma razão válida para esse comportamento devido a ANSI NULLs estarem ON ou OFF, mas isso confunde a mente porque um “== null” direto funcionará como seria de esperar.

Pessoalmente, eu prefiro:

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

sobre

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

porque impede a repetição – embora isso não seja matematicamente exato, mas se encheckbox bem na maioria dos casos.

Eu não sou capaz de comentar o post do divega, mas entre as diferentes soluções apresentadas aqui, a solução da divega produz o melhor SQL. Ambos desempenho sábio e comprimento sábio. Acabei de verificar com o SQL Server Profiler e observando o plano de execução (com “SET STATISTICS PROFILE ON”).

Infelizmente, no Entity Framework 5 DbContext, o problema ainda não foi corrigido.

Eu usei essa solução alternativa (funciona com o MSSQL 2012, mas a configuração ANSI NULLS pode ser preterida em qualquer versão futura do 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(); } } } } 

Deve-se notar que é uma solução alternativa suja, mas é uma que pode ser implementada muito rapidamente e funciona para todas as consultas.

Se você preferir usar a syntax method (lambda) como eu, você poderia fazer a mesma coisa assim:

 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) query).ToTraceString(); } return result; 
 var result = from entry in table where entry.something == value||entry.something == null select entry; 

Use isso