LINQ Single vs First


215

LINQ:

Czy bardziej efektywne jest użycie Single()operatora, First()gdy wiem, że zapytanie zwróci pojedynczy rekord ?

Czy jest jakaś różnica?

Odpowiedzi:


312

Jeśli spodziewasz się pojedynczego rekordu, zawsze dobrze jest wyrazić w kodzie.

Wiem, że inni napisali, dlaczego używasz jednego lub drugiego, ale pomyślałem, że zilustruję, dlaczego NIE powinieneś używać jednego, gdy masz na myśli drugie.

Uwaga: W moim kodu, ja zazwyczaj korzystają FirstOrDefault()i SingleOrDefault(), ale to już inna kwestia.

Weźmy na przykład tabelę, która przechowuje Customersw różnych językach przy użyciu klucza złożonego ( ID, Lang):

DBContext db = new DBContext();
Customer customer = db.Customers.Where( c=> c.ID == 5 ).First();

Powyższy kod wprowadza możliwy błąd logiczny (trudny do prześledzenia). Zwróci więcej niż jeden rekord (zakładając, że masz rekord klienta w wielu językach), ale zawsze zwróci tylko pierwszy rekord ... który może czasem działać ... ale nie inne. To nieprzewidywalne.

Ponieważ Twoim celem jest zwrot jednorazowego Customerużytku Single();

Następujące wywołałoby wyjątek (co w tym przypadku jest potrzebne):

DBContext db = new DBContext();
Customer customer = db.Customers.Where( c=> c.ID == 5 ).Single();

Następnie po prostu uderzasz się w czoło i mówisz do siebie ... OOPS! Zapomniałem pola językowego! Oto poprawna wersja:

DBContext db = new DBContext();
Customer customer = db.Customers.Where( c=> c.ID == 5 && c.Lang == "en" ).Single();

First() jest przydatny w następującym scenariuszu:

DBContext db = new DBContext();
NewsItem newsitem = db.NewsItems.OrderByDescending( n => n.AddedDate ).First();

Zwróci JEDEN obiekt, a ponieważ używasz sortowania, zostanie zwrócony najnowszy rekord.

Używanie, Single()gdy czujesz, że powinno to wyraźnie zwracać 1 rekord, pomoże ci uniknąć błędów logicznych.


76
Zarówno metoda Single, jak i First mogą przyjmować parametr wyrażenia do filtrowania, więc funkcja Where nie jest konieczna. Przykład: Klient klient = db.Customers.Single (c => c.ID == 5);
Josh Noe,

6
@JoshNoe - Jestem ciekawy, czy jest jakaś różnica między nimi customers.Where(predicate).Single() customers.Single(predicate)?
drzaus

9
@drzaus - Logicznie nie, oba filtrują wartości do zwrócenia na podstawie predykatu. Jednak sprawdziłem demontaż, a Where (predicate). Single () ma trzy dodatkowe instrukcje w prostym przypadku, który wykonałem. Tak więc, chociaż nie jestem ekspertem od IL, ale wydaje się, że klienci.Single (predykat) powinien być bardziej wydajny.
Josh Noe,

5
@JoshNoe, jak się okazuje, jest wręcz przeciwnie.
AgentFire,

10
@AgentFire Aby
M. Mimpen

72

Single wyrzuci wyjątek, jeśli znajdzie więcej niż jeden rekord spełniający kryteria. Najpierw zawsze wybierze pierwszy rekord z listy. Jeśli zapytanie zwraca tylko 1 rekord, możesz przejść do First().

Oba zgłoszą InvalidOperationExceptionwyjątek, jeśli kolekcja jest pusta. Alternatywnie możesz użyć SingleOrDefault(). Nie spowoduje to wyrzucenia wyjątku, jeśli lista jest pusta


29

Pojedynczy()

Zwraca pojedynczy określony element zapytania

Kiedy używać : Jeśli dokładnie 1 element jest oczekiwany; nie 0 lub więcej niż 1. Jeśli lista jest pusta lub zawiera więcej niż jeden element, wygeneruje wyjątek „Sekwencja zawiera więcej niż jeden element”

SingleOrDefault ()

Zwraca pojedynczy określony element zapytania lub wartość domyślną, jeśli nie znaleziono żadnego wyniku

When Use : Gdy oczekiwanych jest 0 lub 1 elementów. Zgłasza wyjątek, jeśli lista zawiera 2 lub więcej elementów.

Pierwszy()

Zwraca pierwszy element zapytania z wieloma wynikami.

Gdy używasz : gdy spodziewany jest 1 lub więcej elementów i chcesz tylko pierwszy. Zgłasza wyjątek, jeśli lista nie zawiera żadnych elementów.

FirstOrDefault ()

Zwraca pierwszy element listy z dowolną ilością elementów lub wartością domyślną, jeśli lista jest pusta.

When Use : Gdy oczekuje się wielu elementów i chcesz tylko pierwszy. Lub lista jest pusta i potrzebujesz domyślnej wartości dla określonego typu, takiej samej jak default(MyObjectType). Na przykład: jeśli typ listy list<int>to zwróci pierwszą liczbę z listy lub 0, jeśli lista jest pusta. Jeśli tak list<string>, zwróci pierwszy ciąg z listy lub null, jeśli lista jest pusta.


1
Ładne wyjaśnienie. Zmieniłbym tylko to, że możesz użyć, Firstgdy spodziewany jest 1 lub więcej elementów , nie tylko „więcej niż 1” i FirstOrDefaultdowolna ilość elementów.
Andrew

18

Między tymi dwiema metodami istnieje subtelna, semantyczna różnica.

Służy Singledo pobierania pierwszego (i jedynego) elementu z sekwencji, która powinna zawierać jeden element i nie więcej. Jeśli sekwencja zawiera więcej niż jeden element, twoje wywołanie Singlespowoduje zgłoszenie wyjątku, ponieważ wskazałeś, że powinien być tylko jeden element.

Służy Firstdo pobierania pierwszego elementu z sekwencji, która może zawierać dowolną liczbę elementów. Jeśli sekwencja zawiera więcej niż jeden element, twoje wywołanieFirst nie spowoduje wygenerowania wyjątku, ponieważ wskazałeś, że potrzebujesz tylko pierwszego elementu w sekwencji i nie obchodzi cię, czy istnieje więcej.

Jeśli sekwencja nie zawiera żadnych elementów, oba wywołania metod spowodują zgłoszenie wyjątków, ponieważ obie metody oczekują obecności co najmniej jednego elementu.


17

Jeśli nie chcesz specjalnie zgłaszać wyjątku w przypadku, gdy jest więcej niż jeden element, użyjFirst() .

Oba są wydajne, weź pierwszy przedmiot. First()jest nieco bardziej wydajny, ponieważ nie zawraca sobie głowy sprawdzaniem, czy jest drugi element.

Jedyną różnicą jest to, że Single()oczekuje się, że na początku będzie tylko jeden element w wyliczeniu, i zgłosi wyjątek, jeśli istnieje więcej niż jeden. Państwo skorzystać .Single() jeśli chcemy specjalnie rzucony wyjątek w tym przypadku.


14

O ile pamiętam, Single () sprawdza, czy po pierwszym jest inny element (i rzuca wyjątek, jeśli tak jest), podczas gdy First () zatrzymuje się po otrzymaniu. Oba zgłaszają wyjątek, jeśli sekwencja jest pusta.

Osobiście zawsze używam First ().


2
W SQL produkują First () robi TOP 1, a Single () robi TOP 2, jeśli się nie mylę.
Matthijs Wessels

10

Jeśli chodzi o wydajność: współpracowaliśmy z współpracownikiem nad wydajnością Single vs First (lub SingleOrDefault vs FirstOrDefault) i argumentowałem za tym, aby First (lub FirstOrDefault) był szybszy i poprawić wydajność (chodzi mi o stworzenie naszej aplikacji Biegnij szybciej).

Przeczytałem kilka postów na temat przepełnienia stosu, które debatują nad tym. Niektórzy twierdzą, że niewielki wzrost wydajności uzyskuje się przy użyciu First zamiast Single. Wynika to z faktu, że First po prostu zwraca pierwszy element, a Single musi zeskanować wszystkie wyniki, aby upewnić się, że nie ma duplikatu (tzn .: jeśli znalazł element w pierwszym wierszu tabeli, nadal skanowałby każdy inny wiersz do upewnij się, że nie ma drugiej wartości pasującej do warunku, który spowodowałby błąd). Czułem, że jestem na solidnym gruncie, ponieważ „First” jest szybszy niż „Single”, więc postanowiłem to udowodnić i odłożyć debatę na później.

Ustawiłem test w mojej bazie danych i dodałem 1 000 000 wierszy ID UniqueIdentifier Foreign UniqueIdentifier Info nvarchar (50) (wypełniony łańcuchami liczb od „0” do „999,9999”

Załadowałem dane i ustawiłem ID jako pole klucza podstawowego.

Korzystając z LinqPad, moim celem było pokazanie, że jeśli szukałeś wartości w „Zagranicznych” lub „Informacyjnych” za pomocą Single, byłoby to znacznie gorsze niż użycie First.

Nie potrafię wyjaśnić wyników, które uzyskałem. W prawie każdym przypadku użycie Single lub SingleOrDefault było nieco szybsze. Nie ma to dla mnie żadnego logicznego sensu, ale chciałem się tym podzielić.

Np .: Użyłem następujących zapytań:

var q = TestTables.First(x=>x.Info == "314638") ;
//Vs.
Var q = TestTables.Single(x=>x.Info =="314638") ; //(this was slightly faster to my surprise)

Próbowałem podobnych zapytań w polu klucza „Zagraniczne”, które nie zostały zaindeksowane, by udowodnić, że First jest szybszy, ale Single był zawsze nieco szybszy w moich testach.


2
Jeśli baza danych znajduje się w indeksowanym polu, baza danych nie musi skanować, aby upewnić się, że jest unikalna. Już to wie. Nie ma więc narzutu, a jedynym narzutem po stronie serwera jest zapewnienie, że wróci tylko jeden rekord. Samodzielne przeprowadzanie testów wydajności nie jest rozstrzygające w ten czy inny sposób
mirhagk,

1
Nie sądzę, że te wyniki byłyby takie same na złożonym obiekcie, gdybyś szukał w polu nieużywanym przez IComparer.
Anthony Nichols,

To powinno być kolejne pytanie. Pamiętaj, aby podać źródło swojego testu .
Sinatr

5

Oni są różni. Oba twierdzą, że zestaw wyników nie jest pusty, ale single potwierdza również, że nie ma więcej niż 1 wynik. Osobiście używam Single w przypadkach, w których spodziewam się, że będzie tylko 1 wynik tylko dlatego, że odzyskanie więcej niż 1 wyniku jest błędem i prawdopodobnie powinno być traktowane jako takie.


5

Możesz wypróbować prosty przykład, aby uzyskać różnicę. Wyjątek zostanie zgłoszony w wierszu 3;

        List<int> records = new List<int>{1,1,3,4,5,6};
        var record = records.First(x => x == 1);
        record = records.Single(x => x == 1);

3

Wiele osób, które znam, korzysta z FirstOrDefault (), ale częściej używam SingleOrDefault (), ponieważ często byłoby więcej niespójności danych, gdyby było więcej niż jeden. Dotyczy to jednak LINQ-to-Objects.


-1

Zapisy w podmiocie pracowniczym:

Employeeid = 1: Tylko jeden pracownik z tym identyfikatorem

Firstname = Robert: Więcej niż jeden pracownik o tym nazwisku

Employeeid = 10: Brak pracownika o tym identyfikatorze

Teraz trzeba dokładnie zrozumieć, co Single()i co First()oznacza.

Pojedynczy()

Funkcja Single () służy do zwracania pojedynczego rekordu, który wyjątkowo występuje w tabeli, dlatego poniższe zapytanie zwróci pracownika, którego employeed =1ponieważ mamy tylko jednego pracownika, który Employeedma 1. Jeśli mamy dwa rekordy, EmployeeId = 1wówczas generuje błąd (patrz błąd poniżej w drugim zapytaniu, w którym wykorzystujemy przykład Firstname.

Employee.Single(e => e.Employeeid == 1)

Powyższe zwróci pojedynczy rekord, który ma 1 employeeId

Employee.Single(e => e.Firstname == "Robert")

Powyższe spowoduje zgłoszenie wyjątku, ponieważ w tabeli znajdują się rekordy wielokrotne FirstName='Robert'. Wyjątkiem będzie

InvalidOperationException: Sequence zawiera więcej niż jeden element

Employee.Single(e => e.Employeeid == 10)

To ponownie wyrzuci wyjątek, ponieważ nie istnieje rekord dla id = 10. Wyjątkiem będzie

InvalidOperationException: Sequence nie zawiera elementów.

Gdyż EmployeeId = 10zwróci wartość null, ale gdy Single()go używamy , wygeneruje błąd. Aby obsłużyć błąd zerowy, powinniśmy użyć SingleOrDefault().

Pierwszy()

First () zwraca z wielu rekordów odpowiadające im rekordy posortowane w porządku rosnącym zgodnie z, birthdatewięc zwróci „Robert”, który jest najstarszy.

Employee.OrderBy(e => e. Birthdate)
.First(e => e.Firstname == "Robert")

Powyżej powinien zwrócić najstarszy, Robert zgodnie z DOB.

Employee.OrderBy(e => e. Birthdate)
.First(e => e.Employeeid == 10)

Powyżej wyrzuci wyjątek, ponieważ nie istnieje rekord dla id = 10. Aby uniknąć wyjątku zerowego, powinniśmy FirstOrDefault()raczej użyć First().

Uwaga: Możemy użyć tylko First()/ Single()gdy jesteśmy absolutnie pewni, że nie może zwrócić wartości zerowej.

W obu funkcjach użyj SingleOrDefault () OR FirstOrDefault (), który obsłuży wyjątek zerowy, w przypadku braku znalezionego rekordu zwróci null.


Proszę wyjaśnić swoją odpowiedź.
MrMavavin,

1
@MrMaavin Zaktualizowałem, uprzejmie daj mi znać, czy jest to dla ciebie zrozumiałe?
Szeryf,
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.