Linq do EntityFramework DateTime

108

W mojej aplikacji używam Entity Framework.

Mój stół

-Article
-period
-startDate

Potrzebuję rekordów pasujących do => DateTime.Now > startDate and (startDate + period) > DateTime.Now

Wypróbowałem ten kod, ale teraz działa

Context.Article
    .Where(p => p.StartDate < DateTime.Now)
    .Where(p => p.StartDate.AddDays(p.Period) > DateTime.Now)

Kiedy uruchamiam kod, występuje następujący wyjątek

LINQ to Entities nie rozpoznaje metody „System.DateTime AddDays (Double)”, a tej metody nie można przetłumaczyć na wyrażenie magazynu.

Yucel
źródło
Jaki to typ period? AddDaysjest złą funkcją, jeśli jest double.
Craig Stuntz

Odpowiedzi:

201

W przypadku korzystania z LINQ to Entity Framework predykaty wewnątrz klauzuli Where są tłumaczone na język SQL. Otrzymujesz ten błąd, ponieważ nie ma tłumaczenia na SQL, DateTime.Add()co ma sens.

Szybkim obejściem byłoby odczytanie wyników pierwszej instrukcji Where do pamięci, a następnie użycie LINQ to Objects w celu zakończenia filtrowania:

Context.Article.Where(p => p.StartDate < DateTime.Now)
               .ToList()
               .Where(p => p.StartDate.AddDays(p.Period) > DateTime.Now);

Możesz również wypróbować metodę EntityFunctions.AddDays , jeśli używasz .NET 4.0:

Context.Article.Where(p => p.StartDate < DateTime.Now)
               .Where(p => EntityFunctions.AddDays(p.StartDate, p.Period)
                   > DateTime.Now);

Uwaga: EF 6jest teraz System.Data.Entity.DbFunctions.AddDays.

Justin Niessner
źródło
42
To niebezpieczne obejście. Co się stanie, jeśli funkcja ToList () zwróci ogromną ilość danych?
Stefan P.
7
Dzięki za wskazówkę EntityFunctions.AddDays Używam EF .net 4.0, ale nie wiedziałem o EntityFunctions, zajrzę do tego.
Stefan P.
2
@SaeedAlg - W pierwszym przykładzie konieczne jest wczytanie pozycji do pamięci. W drugim przykładzie zachowałem dwie klauzule Where, aby dopasować oryginalny format. Nawet jeśli skompresujesz te dwa do jednej klauzuli Where, Entity Framework generuje tożsamość SQL, więc tak naprawdę jest to kwestia czytelności.
Justin Niessner
2
@Justin Niessner bardzo dziękuję, próbuję to robić przez cztery godziny,
ratujesz
2
Zapewniając „szybkie” obejście z EF, wszyscy powinniśmy unikać stosowania metod ToList i ToArray. Równie szybkie jest obliczenie poprzedniej daty i porównanie z tą wartością, a to działa w zapytaniu EF bez tworzenia wystąpienia jego wyników w pamięci.
Isaac Llopis
92

Myślę, że właśnie to próbowała zasugerować ta ostatnia odpowiedź, ale zamiast próbować dodawać dni do p.startdat (coś, czego nie można przekonwertować na instrukcję sql), dlaczego nie zrobić czegoś, co można przyrównać do sql:

var baselineDate = DateTime.Now.AddHours(-24);

something.Where(p => p.startdate >= baselineDate)
Andrzej
źródło
2
@Adrian Carr - W tym przypadku może być lepiej, ale nie w tak wielu, jak w EntityFunctionsrozwiązaniu. Tutaj drugi operand nie jest pobierany z innej jednostki w zapytaniu i można go obliczyć przed zapytaniem. Gdyby oba operandy znajdowały się w db, EntityFunctionsrozwiązanie nadal byłoby odpowiednie, podczas gdy rozwiązanie tej odpowiedzi nie działałoby już.
Frédéric
To lepsze rozwiązanie niż rozwiązanie oznaczone jako odpowiedź. Odpowiedź od Justina / zaznaczona odpowiedź zwróci niepotrzebne wyniki z bazy danych. To rozwiązanie faktycznie wysyła datę w SQL, gdzie filtruje i używa SQL do odfiltrowania danych, co jest znacznie wydajniejsze.
Paweł
3

Co powiesz na odjęcie 2 dni od DateTime.Now:

Context.Article
.Where(p => p.StartDate < DateTime.Now)
.Where(p => p.StartDate > DateTime.Now.Subtract(new TimeSpan(2, 0, 0, 0)))

Szczerze mówiąc, nie jestem pewien, co próbujesz osiągnąć, ale może to zadziałać

TimC
źródło
Artykuły zaczną być wyświetlane w StartDate i kończą się po x (okres) dniach. To jest to, co próbuję zrobić. Drugie rozwiązanie Justina Niessnera działało bardzo dobrze w przypadku tego, co chcę zobaczyć
Yucel
3

Jeśli chcesz, aby twoje wyrażenie zostało przetłumaczone na SQL, możesz spróbować użyć

System.Data.Entity.Core.Objects.AddDays.

Właściwie jest oznaczony jako przestarzały, ale działa. Powinien zostać zastąpiony przez System.Data.Entity.DbFunctions.AddDays, ale nie mogę go znaleźć ...

bubi
źródło
To nic więcej niż zaakceptowana odpowiedź sprzed 4 lat!
Andrew Harris,
@AndrewHarris również moja odpowiedź to odpowiedź 4 lata temu. W pierwszej wersji odpowiedzi znajdowała się lista ToList (=> materializacja danych) przed Where (patrz pierwszy komentarz odpowiedzi).
bubi