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.