Linq para EntityFramework DateTime

108

Em meu aplicativo, estou usando o Entity Framework.

Minha mesa

-Article
-period
-startDate

Eu preciso de registros que correspondam a => DateTime.Now > startDate and (startDate + period) > DateTime.Now

Eu tentei este código, mas agora está funcionando

Context.Article
    .Where(p => p.StartDate < DateTime.Now)
    .Where(p => p.StartDate.AddDays(p.Period) > DateTime.Now)

Quando eu executo meu código, ocorre a seguinte exceção

LINQ to Entities não reconhece o método 'System.DateTime AddDays (Double)' e esse método não pode ser convertido em uma expressão de armazenamento.

Yucel
fonte
Que tipo é period? AddDaysé a função errada se for um double.
Craig Stuntz

Respostas:

201

Ao usar LINQ to Entity Framework, seus predicados dentro da cláusula Where são traduzidos para SQL. Você está recebendo esse erro porque não há tradução para SQL DateTime.Add()que faça sentido.

Uma solução rápida seria ler os resultados da primeira instrução Where na memória e, em seguida, usar LINQ to Objects para concluir a filtragem:

Context.Article.Where(p => p.StartDate < DateTime.Now)
               .ToList()
               .Where(p => p.StartDate.AddDays(p.Period) > DateTime.Now);

Você também pode tentar o método EntityFunctions.AddDays se estiver usando .NET 4.0:

Context.Article.Where(p => p.StartDate < DateTime.Now)
               .Where(p => EntityFunctions.AddDays(p.StartDate, p.Period)
                   > DateTime.Now);

Nota: EF 6está agora System.Data.Entity.DbFunctions.AddDays.

Justin Niessner
fonte
42
Esta é uma solução perigosa, o que acontece se ToList () retornar uma grande quantidade de dados?
Stefan P.
7
Obrigado pela dica EntityFunctions.AddDays Estou usando EF .net 4.0, mas não sabia sobre EntityFunctions, vou dar uma olhada.
Stefan P.
2
@SaeedAlg - No primeiro exemplo, é necessário ler os itens na memória. No segundo exemplo, mantive duas cláusulas Where para corresponder ao formato original. Mesmo se você compactar os dois em uma única cláusula Where, o Entity Framework gera a identidade SQL, portanto, é realmente uma questão de legibilidade.
Justin Niessner
2
@Justin Niessner muito obrigado, estou tentando fazer isso por quatro horas, você salva minha vida em segundos, obrigado novamente
Yucel
2
Ao fornecer soluções alternativas "rápidas" com EF, todos nós devemos evitar o uso dos métodos ToList e ToArray. É tão rápido calcular a data anterior e comparar com esse valor, e isso funciona em uma consulta EF sem instanciar seus resultados na memória.
Isaac Llopis
92

Acho que é isso que a última resposta estava tentando sugerir, mas em vez de tentar adicionar dias a p.startdat (algo que não pode ser convertido em uma instrução sql), por que não fazer algo que pode ser igualado a sql:

var baselineDate = DateTime.Now.AddHours(-24);

something.Where(p => p.startdate >= baselineDate)
Andrew
fonte
2
@Adrian Carr - Pode ser melhor para este caso de uso, mas não para tantos quanto a EntityFunctionssolução. Aqui, o segundo operando não é recuperado de outra entidade na consulta e pode ser calculado antes da consulta. Se ambos os operandos fossem encontrados em db, a EntityFunctionssolução ainda seria adequada enquanto a solução desta resposta não funcionaria mais.
Frédéric
Esta é uma solução melhor do que a solução marcada como a resposta. A resposta de Justin / resposta marcada retornará resultados desnecessários do banco de dados. Essa solução, na verdade, envia a data no SQL para filtro e usa o SQL para filtrar os dados, o que tem muito mais desempenho.
Paulo
3

Que tal subtrair 2 dias de DateTime.Now:

Context.Article
.Where(p => p.StartDate < DateTime.Now)
.Where(p => p.StartDate > DateTime.Now.Subtract(new TimeSpan(2, 0, 0, 0)))

Para ser honesto, não tenho certeza do que você está tentando alcançar, mas isso pode funcionar

TimC
fonte
Os artigos começarão a ser exibidos em StartDate e terminarão após x (período) dias. É isso que estou tentando fazer. A segunda solução de Justin Niessner funcionou muito bem para o que eu quero ver
Yucel
3

Se você precisa que sua expressão seja traduzida para SQL, você pode tentar usar

Método System.Data.Entity.Core.Objects.AddDays.

Na verdade, está marcado como obsoleto, mas funciona. Deve ser substituído por System.Data.Entity.DbFunctions.AddDays, mas não consigo encontrá-lo ...

bubi
fonte
Isso adiciona nada mais do que a resposta aceita de 4 anos antes!
Andrew Harris,
@AndrewHarris também minha resposta é resposta de 4 anos atrás. Na primeira versão da resposta havia um ToList (=> materialização dos dados) antes do Where (veja o primeiro comentário da resposta).
bubi