Linq a EntityFramework DateTime

108

En mi aplicación estoy usando Entity Framework.

Mi mesa

-Article
-period
-startDate

Necesito registros que coincidan => DateTime.Now > startDate and (startDate + period) > DateTime.Now

Probé este código pero ahora funciona

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

Cuando ejecuto mi código, ocurre la siguiente excepción

LINQ to Entities no reconoce el método 'System.DateTime AddDays (Double)', y este método no se puede traducir a una expresión de tienda.

Yucel
fuente
¿Qué tipo es period? AddDayses la función incorrecta si es a double.
Craig Stuntz

Respuestas:

201

Al usar LINQ to Entity Framework, sus predicados dentro de la cláusula Where se traducen a SQL. Está recibiendo ese error porque no hay traducción a SQL, lo DateTime.Add()que tiene sentido.

Una solución rápida sería leer los resultados de la primera instrucción Where en la memoria y luego usar LINQ to Objects para finalizar el filtrado:

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

También puede probar el método EntityFunctions.AddDays si está utilizando .NET 4.0:

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

Nota: En EF 6es ahora System.Data.Entity.DbFunctions.AddDays.

Justin Niessner
fuente
42
Esta es una solución peligrosa, ¿qué sucede si ToList () devuelve una gran cantidad de datos?
Stefan P.
7
Gracias por el consejo de EntityFunctions.AddDays Estoy usando EF .net 4.0 pero no sabía sobre EntityFunctions, lo investigaré.
Stefan P.
2
@SaeedAlg: en el primer ejemplo, es necesario leer los elementos en la memoria. En el segundo ejemplo, mantuve dos cláusulas Where para que coincida con el formato original. Incluso si comprime los dos en una sola cláusula Where, Entity Framework genera SQL de identidad, por lo que es realmente una cuestión de legibilidad.
Justin Niessner
2
@Justin Niessner muchas gracias, estoy tratando de hacer eso durante cuatro horas, me salvas la vida en segundos gracias de nuevo
Yucel
2
Al dar soluciones "rápidas" con EF, todos debemos evitar el uso de los métodos ToList y ToArray. Es igual de rápido calcular la fecha previamente y compararla con ese valor, y eso funciona en una consulta EF sin instanciar sus resultados en la memoria.
Isaac Llopis
92

Creo que esto es lo que la última respuesta estaba tratando de sugerir, pero en lugar de intentar agregar días a p.startdat (algo que no se puede convertir en una declaración SQL), ¿por qué no hacer algo que pueda equipararse a SQL?

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

something.Where(p => p.startdate >= baselineDate)
Andrés
fuente
2
@Adrian Carr: puede ser mejor para este caso de uso, pero no para tantos como la EntityFunctionssolución. Aquí, el segundo operando no se recupera de otra entidad en la consulta y se puede calcular antes de la consulta. Si ambos operandos se encontraran en db, la EntityFunctionssolución aún sería adecuada mientras que la solución de esta respuesta ya no funcionaría.
Frédéric
Esta es una mejor solución que la solución marcada como la respuesta. La respuesta de Justin / respuesta marcada devolverá resultados innecesarios de la base de datos. Esta solución en realidad envía la fecha en el SQL donde se filtra y usa SQL para filtrar los datos, que es mucho más eficaz.
Paul
3

¿Qué tal restar 2 días de DateTime. Ahora:

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

Para ser honesto, no estoy seguro de lo que está tratando de lograr, pero esto puede funcionar.

TimC
fuente
Los artículos comenzarán a mostrarse en StartDate y finalizarán después de x (período) días. Esto es lo que estoy tratando de hacer. La segunda solución de Justin Niessner funcionó muy bien para lo que quiero ver
Yucel
3

Si necesita que su expresión se traduzca a SQL, puede intentar usar

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

En realidad está marcado como obsoleto pero funciona. Debería ser reemplazado por System.Data.Entity.DbFunctions.AddDays pero no puedo encontrarlo ...

bubi
fuente
¡Esto no agrega nada más que la respuesta aceptada de 4 años antes!
Andrew Harris
@AndrewHarris también mi respuesta es la respuesta de hace 4 años. En la primera versión de la respuesta había un ToList (=> materialización de datos) antes del Dónde (ver el primer comentario de la respuesta).
bubi