Come confrontare solo i componenti della data da DateTime in EF?


116

Ho due valori di data, uno già memorizzato nel database e l'altro selezionato dall'utente utilizzando DatePicker. Il caso d'uso è cercare una data particolare dal database.

Il valore precedentemente inserito nel database ha sempre una componente temporale di 12:00:00, dove la data inserita dal selettore ha una componente temporale diversa.

Sono interessato solo ai componenti della data e vorrei ignorare il componente dell'ora.

Quali sono i modi per eseguire questo confronto in C #?

Inoltre, come eseguire questa operazione in LINQ?

AGGIORNAMENTO: su LINQ to Entities, quanto segue funziona correttamente.

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

1
Puoi anche dare un'occhiata a questa domanda SO: stackoverflow.com/questions/683037/how-to-compare-dates-in-c/…
Quintin Robinson,

Risposte:


121

NOTA: al momento della stesura di questa risposta, la relazione EF non era chiara (che è stata modificata nella domanda dopo che è stata scritta). Per un approccio corretto con EF, controlla la risposta di Mandeep .


È possibile utilizzare la DateTime.Dateproprietà per eseguire un confronto di sola data.

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

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

34
È facile confrontare la data, ma la domanda è correlata a LINQ to Entities che non è in grado di convertire la proprietà .Date in SQL.
Michaël Carpentier

1
@ MichaëlCarpentier: buon punto. A quanto pare ha comunque risolto il problema dell'OP.
Fredrik Mörk

6
Questo non interroga il database ma piuttosto elabora i dati nel livello CLR / applicazione dopo il fatto. La vera soluzione è utilizzare la funzione EntityFunctions.TruncateTime (..) come specificato nella risposta sotto, poiché invia la query al database e consente l'elaborazione a livello di archiviazione. Senza questo non è possibile utilizzare la logica di confronto delle date nelle clausole Where / Count e quindi eseguire ulteriori query sui dati filtrati, poiché è necessario prima inserire risultati parziali nel livello dell'applicazione, il che può essere un problema in scenari che elaborare grandi quantità di dati.
Marchy

6
@Marchy Sì, EntityFunctions.TruncateTimesembra certamente essere la strada da percorrere in questi giorni (è diventato disponibile in .NET 4, che è stato rilasciato l'anno dopo che è stata posta questa domanda).
Fredrik Mörk

1
utilizzare il metodo System.Data.Entity.DbFunctions.TruncateTime (). È necessario aggiungere un riferimento a EntityFramework
adeel41

132

Usa la classe EntityFunctionsper tagliare la porzione di tempo.

using System.Data.Objects;    

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

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

AGGIORNARE

A partire da EF 6.0 e versioni successive, EntityFunctions è stato sostituito da DbFunctions .


37
Solo una nota EntityFunctionsè stata deprecata a favore di System.Data.Entity.DbFunctions(almeno) EF6. Potrebbe essere stato prima di questo.
pquest

4
Non sarei pronti a saltare a questa soluzione in quanto è molto lento, più informazioni: stackoverflow.com/questions/22776843/...
pajics

Non sembra funzionare con un database SQLite. Ottengo "Errore logico SQL o database mancante senza tale funzione: TruncateTime".
shadowsora

24

Penso che questo potrebbe aiutarti.

Ho fatto un'estensione poiché devo confrontare le date nei repository pieni di dati EF e quindi .Date non era un'opzione poiché non è implementata nella traduzione di LinqToEntities.

Ecco il codice:

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

allora puoi usarlo in questo modo.

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

10

Se utilizzi la Dateproprietà per le entità database, otterrai un'eccezione:

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

Puoi usare qualcosa del genere:

  DateTime date = DateTime.Now.Date;

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

8

Per farlo in LINQ to Entities, devi utilizzare metodi supportati :

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

Brutto, ma funziona e viene eseguito sul server DB.


8

Ecco un modo diverso per farlo, ma è utile solo se SecondDate è una variabile che stai passando:

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

Penso che dovrebbe funzionare


1
Eccellente. Ha funzionato per me. Era l'esplicito che DateTime = x.Date;mi mancava. Se ho usato var, o avevo il valore inline nel confronto, non è riuscito con l'eccezione riportata. Grazie.
Tim Croydon

Sono contento che abbia funzionato, Tim. Ci scusiamo per il ritardo nella risposta: non accedo a SO da un po '.
John Kaster

1
Se cambi e.FirstDate.Value <= endDatein e.FirstDate.Value < endDatepuoi rimuovere il file .AddTicks(-1).
Marco de Zeeuw

@ MarcodeZeeuw hai ragione, anche quello funzionerebbe sicuramente. L'espressione condizionale mostrata è intesa per confronti di date inclusivi di date esatte di inizio e fine (supponendo che i valori dell'intervallo di date vengano passati alla condizione anziché impostati in un frammento di codice). IOW, il condizionale è considerato separato dai valori data / ora .
John Kaster

6

Puoi anche usare questo:

DbFunctions.DiffDays(date1, date2) == 0


4

puoi usare il metodo DbFunctions.TruncateTime () per questo.

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

3

Basta confrontare sempre la proprietà Date di DateTime, invece dell'ora della data completa.

Quando si effettua la query LINQ, utilizzare date.Date nella query, ad esempio:

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

10
Ricevo l'errore "Il tipo di membro specificato" Date "non è supportato in LINQ to Entities. Sono supportati solo gli inizializzatori, i membri di entità e le proprietà di navigazione dell'entità.". qualche idea?
matite

Sì, il tuo provider non gestisce direttamente la proprietà .Date. Dovrai estrarlo e confrontare le date in un secondo momento.
Reed Copsey,

.Date non può essere utilizzato in Linq To Entities, sfortunatamente. Si spera che MS aggiunga presto quel supporto per il sovraccarico
John Kaster

1
Confronta sempre la proprietà Date? Ho cercato su Google questo commento perché mi sono chiesto se questa sia la migliore pratica, ad es. per usare sempre la proprietà Date, anche quando è qualcosa di simile candidate.Date >= base.Date. Teoricamente, l' candidate.Dateora deve essere> = 12:00:00, quindi l'utilizzo della proprietà Date è ridondante, ma mi atterrò al consiglio di Reed.
Stephen Hosking

3

Ecco come lo faccio.

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

// Nota per utenti / programmatori Linq

Questo dovrebbe darti il ​​confronto esatto per controllare se una data rientra nell'intervallo quando lavori con l'input di un utente - ad esempio il selettore di date:

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

dove startdate e enddate sono valori di un selettore di date.


1

Senza tempo che provare in questo modo:

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

È possibile utilizzare il collegamento sottostante per confrontare 2 date senza tempo:

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 funzione Confronta restituisce 3 valori diversi: -1 0 1 che significa dt1> dt2, dt1 = dt2, dt1


Perché non restituisci semplicemente DateTime.Compare (dt1.Date, dt2.Date)? Questo rende tutto ciò di cui hai bisogno.
Johnny Graber

0

Prova questo ... Funziona bene per confrontare le proprietà Date tra due tipi DateTimes:

PS. È una soluzione provvisoria e una pratica davvero pessima, non dovrebbe mai essere usata quando sai che il database può portare migliaia di record ...

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

1
PS: di solito uso in questo modo quando DateTimes ha il valore Time e voglio confrontare solo la data.
Raskunho

2
questa è una pessima soluzione, la query otterrà tutti i record e solo allora filtrerà le date. se il database ha milioni di record, questo li catturerà tutti e solo allora filtrerà le date. PRATICA MOLTO CATTIVA.
Demenziale

1
È una soluzione provvisoria e una pratica davvero pessima, non dovrebbe mai essere utilizzata quando sai che il database può portare migliaia di record.
Raskunho

se aggiungerai il tuo commento alla tua risposta, rimuoverò il mio voto negativo. dovrebbe essere chiaro a chiunque visiti questa pagina che la soluzione che hai proposto è pessima senza dover leggere i commenti.
Demenziale

Sebbene sia una cattiva idea in generale, questo approccio si traduce in prestazioni enormemente migliorate per piccoli set di record (<1000 record circa), a causa del modo sciocco in cui EF traduce i confronti di date in SQL. Ho visto le query passare da più di un minuto a meno di un secondo semplicemente facendo il confronto delle date in memoria invece che in qualsiasi cosa SQL EF genera.
Extragorey
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.