LINQ: Kiedy używać SingleOrDefault vs. FirstOrDefault () z kryteriami filtrowania


505

Rozważ metody rozszerzenia IEnumerable SingleOrDefault()iFirstOrDefault()

Dokumenty MSDN, któreSingleOrDefault :

Zwraca jedyny element sekwencji lub wartość domyślną, jeśli sekwencja jest pusta; ta metoda zgłasza wyjątek, jeśli w sekwencji jest więcej niż jeden element.

natomiast FirstOrDefaultz MSDN (przypuszczalnie przy używaniu OrderBy()lub OrderByDescending()albo wcale),

Zwraca pierwszy element sekwencji

Rozważ kilka przykładowych zapytań, nie zawsze jest jasne, kiedy użyć tych dwóch metod:

var someCust = db.Customers
.SingleOrDefault(c=>c.ID == 5); //unlikely(?) to be more than one, but technically COULD BE

var bobbyCust = db.Customers
.FirstOrDefault(c=>c.FirstName == "Bobby"); //clearly could be one or many, so use First?

var latestCust = db.Customers
.OrderByDescending(x=> x.CreatedOn)
.FirstOrDefault();//Single or First, or does it matter?

Pytanie

Jakie konwencje stosujesz lub sugerujesz , decydując się na użycie SingleOrDefault()i FirstOrDefault()w zapytaniach LINQ?

Odpowiedzi:


465

Za każdym razem, gdy używasz SingleOrDefault, wyraźnie stwierdzasz, że zapytanie powinno dać maksymalnie jeden wynik. Z drugiej strony, gdy FirstOrDefaultjest używane, zapytanie może zwrócić dowolną liczbę wyników, ale stwierdzasz, że chcesz tylko pierwszy.

Osobiście uważam, że semantyka jest bardzo różna, a użycie odpowiedniej, w zależności od oczekiwanych wyników, poprawia czytelność.


164
Bardzo ważną różnicą jest to, że jeśli użyjesz SingleOrDefault w sekwencji z więcej niż jednym elementem, zgłasza to wyjątek.
Kamran Bigdely,

17
@kami, jeśli nie zgłosi wyjątku, byłoby dokładnie tak, jak FirstOrDefault. Wyjątek stanowi to, że jest to SingleOrDefault. Warto przywołać tę kwestię i przybić gwóźdź do trumny różnic.
Fabio S.

17
Muszę powiedzieć, że z punktu widzenia wydajności FirstOrDefault działa około 10 razy szybciej niż SingleOrDefault, używając List <MyClass> z 9 000 000 elementów, klasa zawiera 2 liczby całkowite, a Func zawiera wyszukiwanie tych dwóch liczb całkowitych. Przeszukiwanie 200 razy w pętli zajęło 22 sekundy na var v = list.SingleOrDefault (x => x.Id1 == i && x.Id2 == i); i var v = list.FirstOrDefault (x => x.Id1 == i && x.Id2 == i); około 3 sekund
Chen

6
@BitsandBytesHandyman Jeśli SignleOrDefault nie zgłosi wyjątku, gdy sekwencja zawiera więcej niż jeden element, nie zachowa się dokładnie tak, jak FirstOrDefault. FirstOrDefault zwraca pierwszy element, lub null, jeśli sekwencja jest pusta. SingleOrDefault powinien zwrócić jedyny element, lub null, jeśli sekwencja jest pusta LUB jeśli zawiera więcej niż jeden element, bez generowania wyjątku.
Thanasis Ioannidis

2
@RSW Tak, jestem tego świadomy. Czytając uważnie mój komentarz, mówiłem, co powinien zrobić SingleOrDefault, a nie to, co robi. Ale oczywiście to, co powinno zrobić, jest bardzo subiektywne. Dla mnie wzorzec „SomethingOrDefault” oznacza: Uzyskaj wartość „Coś”. Jeśli „Coś” nie zwróci wartości, zwróć wartość domyślną. Co oznacza, że ​​wartość domyślna powinna zostać zwrócona nawet w przypadku, gdy „Coś” zgłosi wyjątek. Tak więc, gdy Single zgłosi wyjątek, SingleOrDefault powinien zwrócić wartość domyślną, w mojej perspektywie.
Thanasis Ioannidis

585

Jeśli zestaw wyników zwraca 0 rekordów:

  • SingleOrDefault zwraca wartość domyślną dla typu (np. wartość domyślna dla int wynosi 0)
  • FirstOrDefault zwraca wartość domyślną dla typu

Jeśli zestaw wyników zwróci 1 rekord:

  • SingleOrDefault zwraca ten rekord
  • FirstOrDefault zwraca ten rekord

Jeśli zestaw wyników zwraca wiele rekordów:

  • SingleOrDefault zgłasza wyjątek
  • FirstOrDefault zwraca pierwszy rekord

Wniosek:

Jeśli chcesz zgłosić wyjątek, jeśli zestaw wyników zawiera wiele rekordów, użyj SingleOrDefault.

Jeśli zawsze chcesz 1 rekord, bez względu na to, co zawiera zestaw wyników, użyj FirstOrDefault


6
Sugerowałbym, że tak naprawdę rzadko chce się wyjątku, więc przez większość czasu preferowana byłaby FirstOrDefault. Wiem, że przypadki istniałyby po prostu nie tak często imo.
MikeKulls

FirstOrDefaultjest zwracany pierwszy rekord oznacza nowy rekord (ostatni) / stary rekord (pierwszy)? czy możesz mi wyjaśnić?
Duk

@Duk, zależy to od sposobu sortowania nagrań. Możesz użyć OrderBy () lub OrderByDescending () itp. Przed wywołaniem FirstOrDefault. Zobacz przykład kodu OP.
Gan

5
Podoba mi się również ta odpowiedź. Zwłaszcza biorąc pod uwagę, że są sytuacje, w których naprawdę chcesz, aby wyjątek został zgłoszony, ponieważ zamierzasz odpowiednio obsługiwać ten rzadki przypadek w innym miejscu, niż udawać, że tak się nie dzieje. Kiedy chcesz wyjątku, mówisz to wyraźnie, a także zmuszając innych do obsługi po prostu dołącz, dzięki czemu cały system jest bardziej niezawodny.
Francis Rodgers

Jest to bardzo wyraźnie określone, aby można było łatwo zrozumieć.
Nirav Vasoya

243

Jest

  • różnica semantyczna
  • różnica w wydajności

pomiędzy dwoma.

Różnica semantyczna:

  • FirstOrDefault zwraca pierwszą pozycję potencjalnie wielokrotną (lub domyślną, jeśli nie istnieje).
  • SingleOrDefaultzakłada, że ​​istnieje jeden element i zwraca go (lub domyślnie, jeśli nie istnieje). Wiele przedmiotów stanowi naruszenie umowy, zgłaszany jest wyjątek.

Różnica wydajności

  • FirstOrDefaultjest zwykle szybszy, iteruje, dopóki nie znajdzie elementu, i musi iterować całą wyliczalność tylko wtedy, gdy go nie znajdzie. W wielu przypadkach istnieje duże prawdopodobieństwo znalezienia przedmiotu.

  • SingleOrDefaultmusi sprawdzić, czy jest tylko jeden element, a zatem zawsze iteruje całą wyliczalność. Mówiąc ściślej, iteruje, aż znajdzie drugi element i zgłasza wyjątek. Ale w większości przypadków nie ma drugiego elementu.

Wniosek

  • Użyj, FirstOrDefaultjeśli nie obchodzi Cię, ile jest przedmiotów lub gdy nie możesz sobie pozwolić na sprawdzenie wyjątkowości (np. W bardzo dużej kolekcji). Gdy zaznaczysz niepowtarzalność przy dodawaniu elementów do kolekcji, sprawdzenie ich ponownie może być zbyt drogie.

  • Użyj, SingleOrDefaultjeśli nie musisz zbytnio dbać o wydajność i chcesz upewnić się, że założenie pojedynczego elementu jest czytelne dla czytelnika i sprawdzone w czasie wykonywania.

W praktyce używasz First/ FirstOrDefaultczęsto nawet w przypadkach, gdy zakładasz pojedynczy element, aby poprawić wydajność. Nadal powinieneś pamiętać, że Single/ SingleOrDefaultmoże poprawić czytelność (ponieważ określa założenie pojedynczego elementu) i stabilność (ponieważ sprawdza to) i odpowiednio go używać.


16
+1 ”lub gdy nie możesz sobie pozwolić na sprawdzenie niepowtarzalności (np. W bardzo dużej kolekcji).” . Szukałem tego. Dodałbym również wymuszanie wyjątkowości podczas wstawiania i / lub projektowania, zamiast w momencie tworzenia zapytania!
Nawaz

Mogę sobie wyobrazić, że SingleOrDefaultiteruje wiele obiektów podczas korzystania z Linq do Objects, ale czy nie SingleOrDefaultmuszę iterować co najwyżej 2 elementów, jeśli Linq rozmawia na przykład z bazą danych? Zastanawiam się ..
Memet Olsen

3
@memetolsen Rozważ plucie kodu dla dwóch z LINQ na SQL - FirstOrDefault używa Top 1. SingleOrDefault używa Top 2.
Jim Wooley

@JimWooley Chyba źle zrozumiałem słowo „wyliczalne”. Myślałem, że Stefan miał na myśli C # Enumerable.
Memet Olsen

1
@memetolsen poprawny pod względem oryginalnej odpowiedzi, twój komentarz dotyczył bazy danych, więc oferowałem to, co dzieje się od dostawcy. Podczas gdy kod .Net iteruje tylko 2 wartości, baza danych odwiedza tyle rekordów, ile potrzeba, dopóki nie trafi do drugiej spełniającej kryteria.
Jim Wooley,

76

Nikt nie wspomniał, że FirstOrDefault przetłumaczony na SQL ma rekord TOP 1, a SingleOrDefault ma TOP 2, ponieważ musi wiedzieć, że jest więcej niż 1 rekord.


3
Kiedy prowadziłem SingleOrDefault przez LinqPad i VS, nigdy nie dostałem WYBIERZ TOP 2, dzięki FirstOrDefault byłem w stanie uzyskać WYBIERZ TOP 1, ale o ile mogę powiedzieć, że nie otrzymuję WYBIERZ TOP 2
Jamie R Rytlewski

Hej, ja też próbowałem w Linqpad i kwerenda sql mnie przeraziła, ponieważ całkowicie pobiera wszystkie wiersze. Nie jestem pewien, jak to się może stać?
AnyOne

1
Zależy to całkowicie od zastosowanego dostawcy LINQ. Na przykład LINQ na SQL i LINQ na Encje mogą tłumaczyć na SQL na różne sposoby. Właśnie wypróbowałem LINQPad z dostawcą IQ MySql i FirstOrDefault()dodaje, LIMIT 0,1podczas gdy SingleOrDefault()nic nie dodaje.
Lucas,

1
EF Core 2.1 tłumaczy FirstOrDefault na SELECT TOP (1), SingleOrDefault na SELECT TOP (2)
camainc

19

W przypadku LINQ -> SQL:

SingleOrDefault

  • wygeneruje zapytanie takie jak „wybierz * od użytkowników, gdzie userid = 1”
  • Wybierz pasujący rekord, Zgłasza wyjątek, jeśli znaleziono więcej niż jeden rekord
  • Użyj, jeśli pobierasz dane na podstawie kolumny klucza podstawowego / unikalnego

FirstOrDefault

  • wygeneruje zapytanie takie jak „wybierz pierwszą 1 * od użytkowników, gdzie userid = 1”
  • Wybierz pierwsze pasujące wiersze
  • Użyj, jeśli pobierasz dane na podstawie kolumny klucza innego niż podstawowy / unikalny

myślę, że powinieneś usunąć „Wybierz wszystkie pasujące wiersze” z SingleOrDefault
Saim Abdullah

10

Używam SingleOrDefaultw sytuacjach, w których moja logika dyktuje, że będzie to zero lub jeden wynik. Jeśli jest ich więcej, jest to sytuacja błędu, która jest pomocna.


3
Często zdarza mi się, że SingleOrDefault () wyróżnia przypadki, w których nie zastosowałem poprawnego filtrowania w zestawie wyników lub gdzie występuje problem z duplikacjami danych bazowych. Najczęściej używam Single () i SingleOrDefault () nad metodami First ().
TimS

Wpływ na wydajność dla Single () i SingleOrDefault () w LINQ to Objects, jeśli masz dużą wyliczalność, ale podczas rozmowy z bazą danych (np. SQL Server) wykona ona 2 pierwsze wywołania, a jeśli indeksy są poprawnie skonfigurowane, wywołanie nie powinno być drogie i wolałbym szybko zawieść i znaleźć problem z danymi zamiast ewentualnie wprowadzić inne problemy z danymi, biorąc niewłaściwy duplikat podczas wywoływania First () lub FirstOrDefault ().
heartlandcoder

5

SingleOrDefault: Mówisz, że „Co najwyżej” jest jeden element pasujący do zapytania lub domyślny FirstOrDefault: Mówisz, że „Co najmniej” jeden element pasuje do zapytania lub domyślny

Powiedz to głośno następnym razem, gdy będziesz musiał dokonać wyboru, i prawdopodobnie wybierzesz mądrze. :)


5
W rzeczywistości brak wyników jest całkowicie akceptowalnym zastosowaniem FirstOrDefault. More correctly: FirstOrDefault` = Dowolna liczba wyników, ale zależy mi tylko na pierwszym, może też nie być żadnych wyników. SingleOrDefault= Istnieje 1 lub 0 wyników, jeśli jest ich więcej, oznacza to, że gdzieś jest błąd. First= Jest co najmniej jeden wynik i chcę go. Single= Jest dokładnie 1 wynik, nie więcej, nie mniej i chcę tego.
Davy8

4

W twoich przypadkach użyłbym następujących:

wybierz według ID == 5: tutaj możesz użyć SingleOrDefault, ponieważ oczekujesz jednej [lub żadnej] encji, jeśli masz więcej niż jedną encję o identyfikatorze 5, coś jest nie tak i na pewno wyjątek jest godny.

podczas wyszukiwania osób, których imię jest równe „Bobby”, może istnieć więcej niż jeden (całkiem możliwe, pomyślałbym), więc nie powinieneś używać ani Single, ani First, po prostu wybierz za pomocą operacji Where (jeśli „Bobby” zwraca zbyt wiele podmioty, użytkownik musi zawęzić wyszukiwanie lub wybrać jeden ze zwróconych wyników)

zamówienie według daty utworzenia powinno być również wykonane za pomocą operacji Where (mało prawdopodobne, aby istniała tylko jedna jednostka, sortowanie nie przydałoby się zbytnio;) oznacza to jednak, że chcesz posortować WSZYSTKIE jednostki - jeśli chcesz tylko JEDEN, użyj FirstOrDefault, Single wyrzuca za każdym razem, jeśli masz więcej niż jeden byt.


3
Nie zgadzam się. Jeśli identyfikator bazy danych jest kluczem podstawowym, baza danych już wymusza unikalność. Marnowanie cyklu procesora, aby sprawdzić, czy baza danych wykonuje swoje zadanie przy każdym zapytaniu, jest po prostu głupie.
John Henckel,

4

Oba są operatorami elementów i służą do wybierania pojedynczego elementu z sekwencji. Ale istnieje niewielka różnica między nimi. Operator SingleOrDefault () wyrzuciłby wyjątek, jeśli spełniony jest więcej niż jeden element, warunek, w którym jako FirstOrDefault () nie zgłosi żadnego wyjątku dla tego samego. Oto przykład.

List<int> items = new List<int>() {9,10,9};
//Returns the first element of a sequence after satisfied the condition more than one elements
int result1 = items.Where(item => item == 9).FirstOrDefault();
//Throw the exception after satisfied the condition more than one elements
int result3 = items.Where(item => item == 9).SingleOrDefault();

2
„istnieje niewielka różnica między nimi” - To jest duże!
Nikhil Vartak

3

W twoim ostatnim przykładzie:

var latestCust = db.Customers
.OrderByDescending(x=> x.CreatedOn)
.FirstOrDefault();//Single or First, or doesn't matter?

Tak. Jeśli spróbujesz użyć, SingleOrDefault()a zapytanie spowoduje więcej niż rekord, otrzymasz i wyjątek. Jedynym momentem, kiedy możesz bezpiecznie korzystać, SingleOrDefault()jest oczekiwanie tylko 1 i tylko 1 wyniku ...


To jest poprawne. Jeśli otrzymasz 0, otrzymasz także wyjątek.
Dennis Rongo,

1

Tak jak teraz rozumiem SingleOrDefault będzie dobrze, jeśli będziesz wyszukiwać dane, które mają być unikalne, tj. Wymuszone przez ograniczenia DB, takie jak klucz podstawowy.

Czy jest lepszy sposób na zapytanie o klucz podstawowy.

Zakładając, że mój TableAcc ma

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

i chcę zapytać o AccountNumber 987654, używam

var data = datacontext.TableAcc.FirstOrDefault(obj => obj.AccountNumber == 987654);

1

Moim zdaniem FirstOrDefaultjest często nadużywany. W większości przypadków, gdy filtrujesz dane, możesz oczekiwać odzyskania kolekcji elementów pasujących do warunku logicznego lub pojedynczego unikalnego elementu poprzez jego unikalny identyfikator - taki jak użytkownik, książka, post itp. dlaczego możemy nawet powiedzieć, że FirstOrDefault()jest to zapach kodu, nie dlatego, że coś jest z nim nie tak, ale dlatego, że jest używany zbyt często. Ten post na blogu szczegółowo omawia ten temat. IMO przez większość czasu SingleOrDefault()jest znacznie lepszą alternatywą, więc uważaj na ten błąd i upewnij się, że używasz najbardziej odpowiedniej metody, która jasno odzwierciedla twoją umowę i oczekiwania.


-1

Jednej rzeczy brakuje w odpowiedziach ....

Jeśli wyników jest wiele, FirstOrDefault bez zamówienia może przywrócić różne wyniki w oparciu o strategię indeksowania, z której kiedykolwiek korzystał serwer.

Osobiście nie mogę znieść widoku FirstOrDefault w kodzie, ponieważ według mnie programista nie dbał o wyniki. Z rozkazem może być przydatny jako sposób egzekwowania najnowszego / najwcześniejszego. Musiałem rozwiązać wiele problemów spowodowanych przez nieostrożnych programistów używających FirstOrDefault.


-2

Zapytałem Google o użycie różnych metod w GitHub. Odbywa się to poprzez uruchomienie zapytania Google dla każdej metody i ograniczenie zapytania do domeny github.com i rozszerzenia pliku .cs przy użyciu zapytania „site: plik github.com: cs ...”

Wydaje się, że metody First * są częściej stosowane niż metody Single *.

| Method               | Results |
|----------------------|---------|
| FirstAsync           |     315 |
| SingleAsync          |     166 |
| FirstOrDefaultAsync  |     357 |
| SingleOrDefaultAsync |     237 |
| FirstOrDefault       |   17400 |
| SingleOrDefault      |    2950 |

-8

Nie rozumiem, dlaczego używasz, FirstOrDefault(x=> x.ID == key)kiedy to można uzyskać wyniki znacznie szybciej, jeśli używasz Find(key). W przypadku zapytań przy użyciu klucza podstawowego tabeli należy zawsze stosować ogólną zasadę Find(key). FirstOrDefaultpowinien być używany do takich predykatów jak(x=> x.Username == username) itp.

nie zasługiwało to na negatywną opinię, ponieważ nagłówek pytania nie był specyficzny dla linq na DB lub Linq na List / IEnumerable itp.


1
W której jest przestrzeń nazw Find()?
p.campbell

Czy możesz nam powiedzieć? Wciąż czekam na odpowiedź.
Denny


Słowo „IEnumerable” znajduje się w pierwszym wierszu treści pytania. Jeśli przeczytasz tylko tytuł, a nie rzeczywiste pytanie, i w rezultacie opublikujesz niepoprawną odpowiedź, to twój błąd i całkowicie uzasadniony powód, by głosować za IMO.
F1Krazy
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.