¿Cómo comparar solo los componentes de fecha de DateTime en EF?

116

Tengo dos valores de fecha, uno ya almacenado en la base de datos y el otro seleccionado por el usuario usando DatePicker. El caso de uso es buscar una fecha particular en la base de datos.

El valor ingresado previamente en la base de datos siempre tiene un componente de hora de 12:00:00, mientras que la fecha ingresada desde el selector tiene un componente de tiempo diferente.

Solo estoy interesado en los componentes de fecha y me gustaría ignorar el componente de tiempo.

¿Cuáles son las formas de hacer esta comparación en C #?

Además, ¿cómo hacer esto en LINQ?

ACTUALIZACIÓN: En LINQ to Entities, lo siguiente funciona bien.

e => DateTime.Compare(e.FirstDate.Value, SecondDate) >= 0
lapiz pizarra
fuente
1
También puede echar un vistazo a esta pregunta SO: stackoverflow.com/questions/683037/how-to-compare-dates-in-c/…
Quintin Robinson

Respuestas:

121

NOTA: en el momento de escribir esta respuesta, la relación EF no estaba clara (que se editó en la pregunta después de que se escribió esto). Para un enfoque correcto con EF, verifique la respuesta de Mandeeps .


Puede utilizar la DateTime.Datepropiedad para realizar una comparación de solo fecha.

DateTime a = GetFirstDate();
DateTime b = GetSecondDate();

if (a.Date.Equals(b.Date))
{
    // the dates are equal
}
Fredrik Mörk
fuente
34
Es fácil comparar la fecha, pero la pregunta está relacionada con LINQ to Entities, que no puede convertir la propiedad .Date en SQL.
Michaël Carpentier
1
@ MichaëlCarpentier: buen punto. Aparentemente, todavía resolvió el problema del OP.
Fredrik Mörk
6
Esto no consulta la base de datos, sino que procesa los datos en la capa CLR / aplicación después del hecho. La solución real es usar la función EntityFunctions.TruncateTime (..) como se especifica en la respuesta a continuación, ya que envía la consulta a la base de datos y permite que el procesamiento se realice en la capa de almacenamiento. Sin esto, no podría usar la lógica de comparación de fechas en las cláusulas Where / Count y luego consultar los datos filtrados, ya que primero tendría que extraer resultados parciales en la capa de aplicación, lo que puede ser un factor decisivo en escenarios que procesar grandes cantidades de datos.
Marchy
6
@Marchy Sí, EntityFunctions.TruncateTimeciertamente parece ser el camino a seguir en estos días (estuvo disponible en .NET 4, que se lanzó un año después de que se hizo esta pregunta).
Fredrik Mörk
1
utilice el método System.Data.Entity.DbFunctions.TruncateTime (). Necesita agregar una referencia a EntityFramework
adeel41
132

Utilice la clase EntityFunctionspara recortar la parte de tiempo.

using System.Data.Objects;    

var bla = (from log in context.Contacts
           where EntityFunctions.TruncateTime(log.ModifiedDate) ==  EntityFunctions.TruncateTime(today.Date)
           select log).FirstOrDefault();

Fuente: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/84d4e18b-7545-419b-9826-53ff1a0e2a62/

ACTUALIZAR

A partir de EF 6.0 y posteriores, EntityFunctions se reemplaza por DbFunctions .

Mandeep Janjua
fuente
37
Solo una nota EntityFunctionsse ha desaprobado a favor de System.Data.Entity.DbFunctions(al menos) EF6. Puede que haya sido antes.
pquest
4
No me apresuraría a saltar a esta solución ya que es muy lenta, más información: stackoverflow.com/questions/22776843/…
pajics
No parece funcionar con una base de datos SQLite. Me sale "error de lógica SQL o falta de base de datos no existe tal función: TruncateTime".
shadowsora
24

Creo que esto podría ayudarte.

Hice una extensión porque tengo que comparar fechas en repositorios llenos de datos EF y, por lo tanto, .Date no era una opción ya que no está implementado en la traducción de LinqToEntities.

Aquí está el código:

        /// <summary>
    /// Check if two dates are same
    /// </summary>
    /// <typeparam name="TElement">Type</typeparam>
    /// <param name="valueSelector">date field</param>
    /// <param name="value">date compared</param>
    /// <returns>bool</returns>
    public Expression<Func<TElement, bool>> IsSameDate<TElement>(Expression<Func<TElement, DateTime>> valueSelector, DateTime value)
    {
        ParameterExpression p = valueSelector.Parameters.Single();

        var antes = Expression.GreaterThanOrEqual(valueSelector.Body, Expression.Constant(value.Date, typeof(DateTime)));

        var despues = Expression.LessThan(valueSelector.Body, Expression.Constant(value.AddDays(1).Date, typeof(DateTime)));

        Expression body = Expression.And(antes, despues);

        return Expression.Lambda<Func<TElement, bool>>(body, p);
    }

entonces puedes usarlo de esta manera.

 var today = DateTime.Now;
 var todayPosts = from t in turnos.Where(IsSameDate<Turno>(t => t.MyDate, today))
                                      select t);
jrojo
fuente
10

Si usa la Datepropiedad para entidades de base de datos, obtendrá una excepción:

"The specified type member 'Date' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."

Puedes usar algo como esto:

  DateTime date = DateTime.Now.Date;

  var result = from client in context.clients
               where client.BirthDate >= date
                     && client.BirthDate < date.AddDays(1)
               select client;
algreat
fuente
8

Para hacerlo en LINQ to Entities, debe usar métodos compatibles :

var year = someDate.Year;
var month = ...
var q = from r in Context.Records
        where Microsoft.VisualBasic.DateAndTime.Year(r.SomeDate) == year 
              && // month and day

Feo, pero funciona y está hecho en el servidor de base de datos.

Craig Stuntz
fuente
8

Aquí hay una forma diferente de hacerlo, pero solo es útil si SecondDate es una variable que está pasando:

DateTime startDate = SecondDate.Date;
DateTime endDate = startDate.AddDays(1).AddTicks(-1);
...
e => e.FirstDate.Value >= startDate && e.FirstDate.Value <= endDate

Creo que debería funcionar

John Kaster
fuente
1
Excelente. Trabajó para mi. Era lo explícito que DateTime = x.Date;me faltaba. Si usé var, o tenía el valor en línea en la comparación, falló con la excepción informada. Gracias.
Tim Croydon
Me alegro de que haya funcionado, Tim. Perdón por la demora en responder, no he iniciado sesión en SO en un tiempo.
John Kaster
1
Si cambia e.FirstDate.Value <= endDatea e.FirstDate.Value < endDate, puede eliminar el .AddTicks(-1).
Marco de Zeeuw
@MarcodeZeeuw tienes razón, eso definitivamente funcionaría también. La expresión condicional que se muestra está destinada a comparaciones de fechas inclusivas de fechas de inicio y finalización exactas (asumiendo que los valores del rango de fechas se pasarían a la condición en lugar de configurarse en un fragmento de código). IOW, el condicional se considera separado de los valores de fecha y hora. .
John Kaster
6

También puedes usar esto:

DbFunctions.DiffDays(date1, date2) == 0

usuario3829854
fuente
4

puede usar el método DbFunctions.TruncateTime () para esto.

e => DbFunctions.TruncateTime(e.FirstDate.Value) == DbFunctions.TruncateTime(SecondDate);
Duro Vyas
fuente
3

Simplemente compare siempre la propiedad Date de DateTime, en lugar de la fecha y hora completa.

Cuando realice su consulta LINQ, use date.Date en la consulta, es decir:

var results = from c in collection
              where c.Date == myDateTime.Date
              select c;
Reed Copsey
fuente
10
Recibo el error "El miembro de tipo especificado 'Fecha' no es compatible con LINQ to Entities. Solo se admiten inicializadores, miembros de entidad y propiedades de navegación de entidad". ¿Alguna idea?
pencilslate
Sí, su proveedor no maneja la propiedad .Date directamente. Tendrá que sacarlo y comparar las fechas más tarde.
Reed Copsey
.Date no se puede usar en Linq To Entities, desafortunadamente. Con suerte, MS agregará pronto ese soporte de sobrecarga
John Kaster
1
¿ Comparar siempre la propiedad Date? Busqué en Google este comentario porque me preguntaba si esa es la mejor práctica, es decir. para usar siempre la propiedad Date, incluso cuando sea algo como candidate.Date >= base.Date. En teoría, la candidate.Datehora debe ser> = 12:00:00, por lo que usar la propiedad Date es redundante, pero seguiré el consejo de Reed.
Stephen Hosking
3

Así es como hago esto.

DateTime date_time_to_compare = DateTime.Now;
//Compare only date parts
context.YourObject.FirstOrDefault(r =>
                EntityFunctions.TruncateTime(r.date) == EntityFunctions.TruncateTime(date_to_compare));
Alejandro del Río
fuente
2

// Nota para usuarios / codificadores de Linq

Esto debería brindarle la comparación exacta para verificar si una fecha está dentro del rango cuando se trabaja con la entrada de un usuario, por ejemplo, selector de fechas:

((DateTime)ri.RequestX.DateSatisfied).Date >= startdate.Date &&
        ((DateTime)ri.RequestX.DateSatisfied).Date <= enddate.Date

donde fecha de inicio y fecha de finalización son valores de un selector de fechas.

Leo Di Salado
fuente
1

Sin tiempo que intentar así:

TimeSpan ts = new TimeSpan(23, 59, 59);
toDate = toDate.Add(ts);
List<AuditLog> resultLogs = 
    _dbContext.AuditLogs
    .Where(al => al.Log_Date >= fromDate && al.Log_Date <= toDate)
    .ToList();
return resultLogs;
Nalan Madheswaran
fuente
1

Puede utilizar el siguiente enlace para comparar 2 fechas sin tiempo:

private bool DateGreaterOrEqual(DateTime dt1, DateTime dt2)
        {
            return DateTime.Compare(dt1.Date, dt2.Date) >= 0;
        }

private bool DateLessOrEqual(DateTime dt1, DateTime dt2)
        {
            return DateTime.Compare(dt1.Date, dt2.Date) <= 0;
        }

la función Comparar devuelve 3 valores diferentes: -1 0 1 que significa dt1> dt2, dt1 = dt2, dt1

majid
fuente
¿Por qué no devuelve DateTime.Compare (dt1.Date, dt2.Date)? Esto hace todo lo que necesita.
Johnny Graber
0

Pruebe esto ... Funciona bien para comparar las propiedades de fecha entre dos tipos de fecha y hora:

PD. Es una solución provisional y una práctica realmente mala, nunca debe usarse cuando se sabe que la base de datos puede traer miles de registros ...

query = query.ToList()
             .Where(x => x.FirstDate.Date == SecondDate.Date)
             .AsQueryable();
Raskunho
fuente
1
PD: Por lo general, uso de esta manera cuando DateTimes tiene un valor de Hora y quiero comparar solo la Fecha.
Raskunho
2
esta es una muy mala solución, la consulta obtendrá todos los registros y solo entonces filtrará las fechas. si la base de datos tiene millones de registros, esto los tomará todos y solo entonces filtrará las fechas. MUY MALA PRACTICA.
Dementic
1
Es una solución provisional y una práctica realmente mala, nunca debe usarse cuando se sabe que la base de datos puede traer miles de registros.
Raskunho
si agrega su comentario en su respuesta, eliminaré mi voto negativo. Debe quedar claro para cualquiera que visite esta página que la solución que propuso es mala sin tener que leer los comentarios.
Dementic
Si bien es una mala idea en general, este enfoque da como resultado un rendimiento enormemente mejorado para conjuntos de registros pequeños (<1000 registros aproximadamente), debido a la forma tonta en que EF traduce las comparaciones de fechas a SQL. He visto consultas pasar de más de un minuto a menos de un segundo simplemente haciendo la comparación de fechas en la memoria en lugar de lo que genere SQL EF.
Extragorey