Comment comparer uniquement les composants de date de DateTime dans EF?


116

J'ai deux valeurs de date, l'une déjà stockée dans la base de données et l'autre sélectionnée par l'utilisateur à l'aide de DatePicker. Le cas d'utilisation est de rechercher une date particulière dans la base de données.

La valeur précédemment entrée dans la base de données a toujours une composante d'heure de 12:00:00, alors que la date entrée à partir du sélecteur a une composante d'heure différente.

Je ne suis intéressé que par les composants de date et je voudrais ignorer le composant de temps.

Quelles sont les façons de faire cette comparaison en C #?

Aussi, comment faire cela dans LINQ?

MISE À JOUR: sur LINQ to Entities, ce qui suit fonctionne correctement.

e => DateTime.Compare(e.FirstDate.Value, SecondDate) >= 0

1
Vous pouvez également jeter un oeil à cette question SO: stackoverflow.com/questions/683037/how-to-compare-dates-in-c/...
Quintin Robinson

Réponses:


121

REMARQUE: au moment de la rédaction de cette réponse, la relation EF n'était pas claire (qui a été modifiée dans la question après avoir été écrite). Pour une approche correcte avec EF, vérifiez la réponse de Mandeeps .


Vous pouvez utiliser la DateTime.Datepropriété pour effectuer une comparaison de date uniquement.

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

if (a.Date.Equals(b.Date))
{
    // the dates are equal
}

34
Il est facile de comparer la date, mais la question est liée à LINQ aux entités qui ne peuvent pas convertir la propriété .Date en SQL.
Michaël Carpentier

1
@ MichaëlCarpentier: bon point. Apparemment, cela a toujours résolu le problème du PO.
Fredrik Mörk

6
Cela n'interroge pas la base de données, mais traite les données dans la couche CLR / application après coup. La vraie solution est d'utiliser la fonction EntityFunctions.TruncateTime (..) comme spécifié dans la réponse ci-dessous, car elle envoie la requête à la base de données et permet le traitement au niveau de la couche de stockage. Sans cela, vous ne pourriez pas utiliser la logique de comparaison de dates dans les clauses Where / Count, puis interroger davantage les données filtrées, car vous devez d'abord extraire des résultats partiels dans la couche application, ce qui peut être un facteur décisif dans les scénarios qui traiter de grandes masses de données.
Marchy

6
@Marchy Oui, EntityFunctions.TruncateTimesemble certainement être la voie à suivre ces jours-ci (il est devenu disponible dans .NET 4 qui a été publié l'année après que cette question a été posée).
Fredrik Mörk

1
utilisez la méthode System.Data.Entity.DbFunctions.TruncateTime (). Vous devez ajouter une référence à EntityFramework
adeel41

132

Utilisez la classe EntityFunctionspour découper la partie temporelle.

using System.Data.Objects;    

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

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

METTRE À JOUR

À partir d'EF 6.0 et versions ultérieures, EntityFunctions est remplacé par DbFunctions .


37
Juste une note EntityFunctionsa été déconseillée au profit de System.Data.Entity.DbFunctionspour (au moins) EF6. C'était peut-être plus tôt que ça.
pquest le

4
Je ne serais pas rapide pour passer à cette solution car elle est vraiment lente, plus d'infos: stackoverflow.com/questions/22776843/…
pajics

Ne semble pas fonctionner avec une base de données SQLite. J'obtiens "erreur de logique SQL ou base de données manquante aucune fonction de ce type: TruncateTime".
shadowsora

24

Je pense que cela pourrait vous aider.

J'ai fait une extension car je dois comparer les dates dans des référentiels remplis de données EF et donc .Date n'était pas une option car il n'est pas implémenté dans la traduction LinqToEntities.

Voici le code:

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

alors vous pouvez l'utiliser de cette manière.

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

10

Si vous utilisez la Datepropriété pour les entités DB, vous obtiendrez une exception:

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

Vous pouvez utiliser quelque chose comme ceci:

  DateTime date = DateTime.Now.Date;

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

8

Pour le faire dans LINQ to Entities, vous devez utiliser les méthodes prises en charge :

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

Moche, mais ça marche, et c'est fait sur le serveur DB.


8

Voici une manière différente de le faire, mais ce n'est utile que si SecondDate est une variable que vous passez:

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

Je pense que ça devrait marcher


1
Excellent. A travaillé pour moi. C'était l'explicite qui DateTime = x.Date;me manquait. Si j'utilisais varou avais la valeur en ligne dans la comparaison, cela échouait avec l'exception signalée. Merci.
Tim Croydon

Heureux que cela ait fonctionné, Tim. Désolé pour le retard dans la réponse - je ne me suis pas connecté à SO depuis un moment.
John Kaster

1
Si vous changez e.FirstDate.Value <= endDateen, e.FirstDate.Value < endDatevous pouvez supprimer le fichier .AddTicks(-1).
Marco de Zeeuw

@MarcodeZeeuw vous avez raison, cela fonctionnerait certainement aussi. L'expression conditionnelle affichée est destinée à des comparaisons de date inclusives d'heures exactes de début et de fin (en supposant que les valeurs de la plage de dates seraient transmises à la condition plutôt que configurées dans un fragment de code.) IOW, le conditionnel est considéré comme distinct des valeurs de date / heure. .
John Kaster

6

Vous pouvez également utiliser ceci:

DbFunctions.DiffDays(date1, date2) == 0


4

vous pouvez utiliser la méthode DbFunctions.TruncateTime () pour cela.

e => DbFunctions.TruncateTime(e.FirstDate.Value) == DbFunctions.TruncateTime(SecondDate);

3

Comparez toujours la propriété Date de DateTime, au lieu de la date complète.

Lorsque vous effectuez votre requête LINQ, utilisez date.Date dans la requête, c'est-à-dire:

var results = from c in collection
              where c.Date == myDateTime.Date
              select c;

10
J'obtiens l'erreur "Le membre de type spécifié 'Date' n'est pas pris en charge dans LINQ to Entities. Seuls les initialiseurs, les membres d'entité et les propriétés de navigation d'entité sont pris en charge.". Des pensées?
crayonslate le

Ouais, votre fournisseur ne gère pas directement la propriété .Date. Vous devrez le retirer et comparer les dates plus tard.
Reed Copsey

.Date ne peut malheureusement pas être utilisé dans les entités Linq To. Espérons que MS ajoutera bientôt ce support de surcharge
John Kaster

1
Toujours comparer la propriété Date? J'ai cherché sur Google ce commentaire parce que je me suis demandé si c'était la meilleure pratique, c'est-à-dire. pour toujours utiliser la propriété Date, même si c'est quelque chose comme candidate.Date >= base.Date. Théoriquement, l' candidate.Dateheure doit être> = 12:00:00, donc l'utilisation de la propriété Date est redondante, mais je m'en tiendrai aux conseils de Reed.
Stephen Hosking

3

C'est comme ça que je fais ça.

DateTime date_time_to_compare = DateTime.Now;
//Compare only date parts
context.YourObject.FirstOrDefault(r =>
                EntityFunctions.TruncateTime(r.date) == EntityFunctions.TruncateTime(date_to_compare));

2

// Remarque pour les utilisateurs / codeurs Linq

Cela devrait vous donner la comparaison exacte pour vérifier si une date tombe dans la plage lorsque vous travaillez avec l'entrée d'un utilisateur - sélecteur de date par exemple:

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

où startdate et enddate sont des valeurs d'un sélecteur de date.


1

Sans le temps, essayez comme ceci:

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;

1

Vous pouvez utiliser le lien ci-dessous pour comparer 2 dates sans heure:

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 fonction Compare renvoie 3 valeurs différentes: -1 0 1 ce qui signifie dt1> dt2, dt1 = dt2, dt1


Pourquoi ne renvoyez-vous pas simplement DateTime.Compare (dt1.Date, dt2.Date)? Cela fait tout ce dont vous avez besoin.
Johnny Graber

0

Essayez ceci ... Cela fonctionne bien pour comparer les propriétés Date entre deux types DateTimes:

PS. C'est une solution provisoire et une très mauvaise pratique, ne doit jamais être utilisée quand on sait que la base de données peut apporter des milliers d'enregistrements ...

query = query.ToList()
             .Where(x => x.FirstDate.Date == SecondDate.Date)
             .AsQueryable();

1
PS: J'utilise généralement cette méthode lorsque les DateTimes ont une valeur Heure et que je veux comparer uniquement la Date.
Raskunho

2
c'est une très mauvaise solution, la requête obtiendra tous les enregistrements et filtrera ensuite les dates. si la base de données contient des millions d'enregistrements, cela les capturera tous et ce n'est qu'alors que filtrera les dates. TRÈS MAUVAISE PRATIQUE.
Dementic

1
C'est une solution provisoire et une très mauvaise pratique, ne devrait jamais être utilisée lorsque vous savez que la base de données peut contenir des milliers d'enregistrements.
Raskunho

si vous ajoutez votre commentaire dans votre réponse, je supprimerai mon vote négatif. il devrait être clair pour toute personne visitant cette page que la solution que vous avez proposée est mauvaise sans avoir à lire les commentaires.
Dementic

Bien que ce soit une mauvaise idée en général, cette approche se traduit par des performances considérablement améliorées pour les petits jeux d'enregistrements (<1000 enregistrements ou plus), en raison de la manière stupide qu'EF traduit les comparaisons de dates en SQL. J'ai vu des requêtes passer de plus d'une minute à moins d'une seconde simplement en faisant la comparaison de date en mémoire au lieu de ce que SQL EF génère.
Extragorey
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.