LINQ To Entities nie rozpoznaje metody Last. Naprawdę?


144

W tym zapytaniu:

public static IEnumerable<IServerOnlineCharacter> GetUpdated()
{
    var context = DataContext.GetDataContext();
    return context.ServerOnlineCharacters
        .OrderBy(p => p.ServerStatus.ServerDateTime)
        .GroupBy(p => p.RawName)
        .Select(p => p.Last());
}

Musiałem to przełączyć, aby działało

public static IEnumerable<IServerOnlineCharacter> GetUpdated()
{
    var context = DataContext.GetDataContext();
    return context.ServerOnlineCharacters
        .OrderByDescending(p => p.ServerStatus.ServerDateTime)
        .GroupBy(p => p.RawName)
        .Select(p => p.FirstOrDefault());
}

Nie mogłem nawet użyć p.First(), aby odzwierciedlić pierwsze zapytanie.

Dlaczego istnieją takie podstawowe ograniczenia w innym tak solidnym systemie ORM?


zapisz swój obiekt IEnumrable w nowej zmiennej, a następnie zwróć variable.last (). to będzie działać.
Ali.Rashidi

Odpowiedzi:


220

To ograniczenie sprowadza się do faktu, że ostatecznie musi przetłumaczyć to zapytanie na SQL, a SQL ma SELECT TOP(w T-SQL), ale nie ma SELECT BOTTOM(nic takiego).

Jest jednak łatwy sposób na obejście tego problemu, po prostu zamów malejąco, a następnie zrób First(), co zrobiłeś.

EDYCJA: Inni dostawcy prawdopodobnie będą mieli różne implementacje SELECT TOP 1, w Oracle prawdopodobnie będzie to coś bardziej podobnegoWHERE ROWNUM = 1

EDYTOWAĆ:

Kolejna mniej wydajna alternatywa - NIE POLECAM! - polega na wywołaniu .ToList()twoich danych wcześniej .Last(), co natychmiast wykona wyrażenie LINQ To Entities, które zostało zbudowane do tego momentu, a następnie twoja .Last () zadziała, ponieważ w tym momencie .Last()jest skutecznie wykonywana w kontekście Zamiast tego LINQ to Objects Expression. (I jak zauważyłeś, może to przywrócić tysiące rekordów i zmarnować mnóstwo zmaterializowanych obiektów CPU, które nigdy nie zostaną wykorzystane)

Ponownie nie polecałbym robienia tego drugiego, ale pomaga to zilustrować różnicę między miejscem i czasem wykonywania wyrażenia LINQ.


i jak LINQ To SQL radzi sobie z tym scenariuszem?
bevacqua

@Neil tak, wiem, że mogę wywołać ToList, ale wolałbym nie pobierać tysięcy rekordów z bazy danych tylko po to, aby je przefiltrować do pięciu rekordów
bevacqua

2
Jeśli wiesz, że Twoje zapytanie zwróci małe wyniki, dzwonienie ToListnie jest takie złe.
Justin Skiles,

35

Zamiast tego Last()spróbuj tego:

model.OrderByDescending(o => o.Id).FirstOrDefault();

14

Zamień Last()na selektor LinqOrderByDescending(x => x.ID).Take(1).Single()

Coś takiego działałoby, gdybyś wolał zrobić to w Linq:

public static IEnumerable<IServerOnlineCharacter> GetUpdated()
{
    var context = DataContext.GetDataContext();
    return context.ServerOnlineCharacters.OrderBy(p => p.ServerStatus.ServerDateTime).GroupBy(p => p.RawName).Select(p => p.OrderByDescending(x => x.Id).Take(1).Single());
}

1
Czy jest jakiś powód, aby używać .Take (1) .Single () zamiast .FirstOrDefault ()?
Tot Zam

2
@TotZam Prawidłowym zamiennikiem będzie w tym przypadku .First (), ponieważ Single () zgłasza wyjątek, jeśli liczba elementów nie wynosi dokładnie 1.
MEMark

0

Jeszcze inny sposób, aby uzyskać ostatni element bez OrderByDescending i załadować wszystkie encje:

dbSet
    .Where(f => f.Id == dbSet.Max(f2 => f2.Id))
    .FirstOrDefault();

0

Dzieje się tak, ponieważ LINQ to Entities (i ogólnie bazy danych) nie obsługuje wszystkich metod LINQ (szczegółowe informacje można znaleźć tutaj: http://msdn.microsoft.com/en-us/library/bb738550.aspx )

Potrzebujesz tutaj uporządkować swoje dane w taki sposób, aby „ostatni” rekord stał się „pierwszy”, a następnie możesz użyć FirstOrDefault. Zauważ, że bazy danych zazwyczaj nie mają takich pojęć jak „pierwszy” i „ostatni”, to nie jest tak, że ostatnio wstawiony rekord będzie „ostatni” w tabeli.

Ta metoda może rozwiązać twój problem

db.databaseTable.OrderByDescending(obj => obj.Id).FirstOrDefault();

-2

Dodanie pojedynczej funkcji, AsEnumerable()zanim funkcja Select zadziałała dla mnie.
Przykład:

return context.ServerOnlineCharacters
    .OrderByDescending(p => p.ServerStatus.ServerDateTime)
    .GroupBy(p => p.RawName).AsEnumerable()
    .Select(p => p.FirstOrDefault());

Odniesienia: https://www.codeproject.com/Questions/1005274/LINQ-to-Entities-does-not-recognize-the-method-Sys


Zaleca się włączenie działającego kodu z linku do odpowiedzi. Odpowiedzi zawierające tylko łącza przyciągną negatywną uwagę. Podaj pełną odpowiedź, dodając znaleziony kod, aby pomóc i rozwiązać problem. To rozwiązuje problem niedziałających linków z powodu błędów 404 w przyszłości.
Studocwho

1
dodał przykład do mojej odpowiedzi
Artem Levitin

Wadą tej odpowiedzi jest to, że przyniesie ona wszystkie wyniki przed stroną serwera „AsEnumerable”, a następnie wybierze pierwszy. Może to być bardzo niepożądane. (Miałem taką sytuację, w której wyniki zajmowały ponad 20 sekund z powodu 20k + rekordów przeniesionych po stronie serwera, gdy przeniosłem je z powrotem na stronę DB, wyniki wróciły w mniej niż sekundę)
TChadwick
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.