Jak porównać tylko składniki daty z DateTime w EF?

116

Mam dwie wartości dat, jedną już przechowywaną w bazie danych, a drugą wybraną przez użytkownika za pomocą DatePicker. Przypadkiem użycia jest wyszukanie określonej daty w bazie danych.

Wartość wprowadzona wcześniej do bazy zawsze ma składową czasową o godzinie 12:00:00, gdzie jako data wprowadzona z selektora ma inny składnik czasu.

Interesują mnie tylko składniki daty i chciałbym zignorować składnik czasu.

Jakie są sposoby wykonania tego porównania w C #?

Jak to zrobić w LINQ?

AKTUALIZACJA: W LINQ to Entities, poniższe działa dobrze.

e => DateTime.Compare(e.FirstDate.Value, SecondDate) >= 0
ołówek
źródło
1
Możesz również rzucić okiem na to pytanie SO: stackoverflow.com/questions/683037/how-to-compare-dates-in-c/…
Quintin Robinson

Odpowiedzi:

121

UWAGA: w momencie pisania tej odpowiedzi relacja WF była niejasna (która została dodana do pytania po napisaniu tego). Aby uzyskać prawidłowe podejście z EF, sprawdź odpowiedź Mandeeps .


Możesz użyć tej DateTime.Datewłaściwości, aby przeprowadzić porównanie tylko daty.

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

if (a.Date.Equals(b.Date))
{
    // the dates are equal
}
Fredrik Mörk
źródło
34
Porównywanie dat jest łatwe, ale pytanie dotyczy LINQ to Entities, które nie mogą przekonwertować właściwości .Date na SQL.
Michaël Carpentier
1
@ MichaëlCarpentier: słuszna uwaga. Najwyraźniej nadal rozwiązało to problem PO.
Fredrik Mörk
6
To nie wysyła zapytania do bazy danych, ale raczej przetwarza dane w warstwie CLR / aplikacji po fakcie. Prawdziwym rozwiązaniem jest użycie funkcji EntityFunctions.TruncateTime (..), jak określono w odpowiedzi poniżej, ponieważ wysyła ona zapytanie do bazy danych i umożliwia przetwarzanie w warstwie magazynowania. Bez tego nie można by użyć logiki porównywania dat w klauzulach Where / Count, a następnie wykonać dalsze zapytania dotyczące przefiltrowanych danych, ponieważ najpierw musiałbyś pobrać częściowe wyniki do warstwy aplikacji, co może być przełomem w scenariuszach, które przetwarzać duże zbiory danych.
Marchy
6
@Marchy Tak, z EntityFunctions.TruncateTimepewnością wydaje się, że jest to najlepszy sposób w dzisiejszych czasach (stało się dostępne w .NET 4, który został wydany rok po zadaniu tego pytania).
Fredrik Mörk
1
użyj metody System.Data.Entity.DbFunctions.TruncateTime (). Musisz dodać odwołanie do EntityFramework
adeel41
132

Użyj klasy EntityFunctionsdo przycinania części czasu.

using System.Data.Objects;    

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

Źródło: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/84d4e18b-7545-419b-9826-53ff1a0e2a62/

AKTUALIZACJA

Od EF 6,0 i nowszych EntityFunctions jest zastępowane przez DbFunctions .

Mandeep Janjua
źródło
37
Po prostu uwaga EntityFunctionszostała wycofana na korzyść System.Data.Entity.DbFunctions(przynajmniej) EF6. To mogło być wcześniej.
pquest
4
Nie chciałbym szybko przeskoczyć do tego rozwiązania, ponieważ jest bardzo powolne, więcej informacji: stackoverflow.com/questions/22776843/…
pajics
Wydaje się, że nie działa z bazą danych SQLite. Otrzymuję komunikat „Błąd logiki SQL lub brak bazy danych, brak takiej funkcji: TruncateTime”.
shadowsora
24

Myślę, że to mogłoby ci pomóc.

Zrobiłem rozszerzenie, ponieważ muszę porównać daty w repozytoriach wypełnionych danymi EF, więc .Data nie była opcją, ponieważ nie jest zaimplementowana w tłumaczeniu LinqToEntities.

Oto kod:

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

wtedy możesz go użyć w ten sposób.

 var today = DateTime.Now;
 var todayPosts = from t in turnos.Where(IsSameDate<Turno>(t => t.MyDate, today))
                                      select t);
jrojo
źródło
10

Jeśli użyjesz tej Datewłaściwości dla jednostek DB, otrzymasz wyjątek:

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

Możesz użyć czegoś takiego:

  DateTime date = DateTime.Now.Date;

  var result = from client in context.clients
               where client.BirthDate >= date
                     && client.BirthDate < date.AddDays(1)
               select client;
algreat
źródło
8

Aby to zrobić w LINQ to Entities, musisz użyć obsługiwanych metod :

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

Brzydkie, ale działa i odbywa się na serwerze DB.

Craig Stuntz
źródło
8

Oto inny sposób, aby to zrobić, ale jest to przydatne tylko wtedy, gdy SecondDate jest zmienną, którą przekazujesz:

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

Myślę, że to powinno działać

John Kaster
źródło
1
Doskonały. Pracował dla mnie. DateTime = x.Date;Brakowało mi tego wyraźnego . Gdybym użył varlub miał wartość wbudowaną w porównaniu, nie powiodło się i zgłosił wyjątek. Dzięki.
Tim Croydon
Cieszę się, że zadziałało, Tim. Przepraszam za opóźnienie w odpowiedzi - tak naprawdę nie logowałem się do SO od jakiegoś czasu.
John Kaster,
1
Jeśli zmienisz e.FirstDate.Value <= endDatena e.FirstDate.Value < endDate, możesz usunąć .AddTicks(-1).
Marco de Zeeuw
@MarcodeZeeuw masz rację, to na pewno też by działało. Wyświetlane wyrażenie warunkowe jest przeznaczone do obejmującego porównania dat dokładnych początkowych i końcowych dat dat (zakładając, że wartości zakresu dat zostaną przekazane do warunku, a nie ustawione we fragmencie kodu). IOW, warunek jest traktowany jako oddzielny od wartości daty i godziny .
John Kaster,
6

Możesz również użyć tego:

DbFunctions.DiffDays(date1, date2) == 0

user3829854
źródło
4

możesz użyć do tego metody DbFunctions.TruncateTime ().

e => DbFunctions.TruncateTime(e.FirstDate.Value) == DbFunctions.TruncateTime(SecondDate);
Surowe Vyas
źródło
3

Po prostu zawsze porównuj właściwość Date z DateTime zamiast pełnej daty i godziny.

Kiedy tworzysz zapytanie LINQ, użyj date.Date w zapytaniu, tj .:

var results = from c in collection
              where c.Date == myDateTime.Date
              select c;
Reed Copsey
źródło
10
Otrzymuję błąd „Data” elementu członkowskiego określonego typu nie jest obsługiwana w LINQ to Entities. Obsługiwane są tylko inicjatory, elementy członkowskie jednostki i właściwości nawigacji jednostek. ”. jakieś pomysły?
ołówek,
Tak - Twój dostawca nie obsługuje bezpośrednio właściwości .Date. Będziesz musiał go wyciągnąć i porównać daty później.
Reed Copsey,
.Date nie może być niestety używane w Linq To Entities. Miejmy nadzieję, że MS wkrótce doda obsługę przeciążenia
John Kaster,
1
Zawsze porównuj właściwość Date? Wpisałem się w ten komentarz, ponieważ zastanawiałem się, czy to najlepsza praktyka, tj. aby zawsze używać właściwości Date, nawet jeśli jest to coś podobnego candidate.Date >= base.Date. Teoretycznie candidate.Dateczas musi wynosić> = 12:00:00, więc używanie właściwości Date jest zbędne, ale będę trzymać się rady Reeda.
Stephen Hosking
3

Oto jak to robię.

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
źródło
2

// Uwaga dla użytkowników / programistów Linq

Powinno to dać ci dokładne porównanie w celu sprawdzenia, czy data mieści się w zakresie podczas pracy z danymi wejściowymi od użytkownika - na przykład:

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

gdzie data początkowa i data końcowa to wartości z selektora dat.

Leo Di Salty
źródło
1

Bez czasu spróbuj tak:

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
źródło
1

Możesz użyć poniższego linku, aby porównać 2 daty bez czasu:

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

Funkcja Compare zwraca 3 różne wartości: -1 0 1 co oznacza dt1> dt2, dt1 = dt2, dt1

majid
źródło
Dlaczego po prostu nie zwrócisz DateTime.Compare (dt1.Date, dt2.Date)? To wszystko, czego potrzebujesz.
Johnny Graber
0

Spróbuj tego ... Dobrze jest porównać właściwości Date między dwoma typami DateTimes:

PS. Jest to tymczasowe rozwiązanie i naprawdę zła praktyka, nigdy nie należy jej używać, gdy wiesz, że baza danych może przynieść tysiące rekordów ...

query = query.ToList()
             .Where(x => x.FirstDate.Date == SecondDate.Date)
             .AsQueryable();
Raskunho
źródło
1
PS: Zwykle używam tego sposobu, gdy DateTimes mają wartość Time i chcę porównać tylko datę.
Raskunho,
2
jest to bardzo złe rozwiązanie, kwerenda pobierze wszystkie rekordy, a dopiero potem odfiltruje daty. jeśli baza danych zawiera miliony rekordów, spowoduje to przechwycenie ich wszystkich i dopiero wtedy przefiltruje daty. BARDZO ZŁA PRAKTYKA.
Dementic
1
Jest to tymczasowe rozwiązanie i naprawdę zła praktyka; nigdy nie powinno się go stosować, gdy wiesz, że baza danych może przynieść tysiące rekordów.
Raskunho
jeśli dodasz swój komentarz do swojej odpowiedzi, usunę mój głos negatywny. dla każdego odwiedzającego tę stronę powinno być jasne, że proponowane rozwiązanie jest złe bez konieczności czytania komentarzy.
Dementic
Chociaż ogólnie jest to zły pomysł, to podejście skutkuje ogromną poprawą wydajności dla małych zestawów rekordów (mniej więcej 1000 rekordów), ze względu na głupi sposób, w jaki EF tłumaczy porównania dat na SQL. Widziałem zapytania trwające od ponad minuty do poniżej sekundy, po prostu wykonując porównanie dat w pamięci zamiast w tym, co generuje SQL EF.
Extragorey