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

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
}

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 .


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

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;

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.


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ć


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,


4

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

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

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;

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

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.


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;

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


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();

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.