LINQ: Wann wird SingleOrDefault vs. FirstOrDefault () mit Filterkriterien verwendet?

506

Betrachten Sie die IEnumerable-Erweiterungsmethoden SingleOrDefault()undFirstOrDefault()

MSDN dokumentiert, dassSingleOrDefault :

Gibt das einzige Element einer Sequenz oder einen Standardwert zurück, wenn die Sequenz leer ist. Diese Methode löst eine Ausnahme aus, wenn die Sequenz mehr als ein Element enthält.

wohingegen FirstOrDefaultvon MSDN (vermutlich , wenn eine Verwendung OrderBy()oder OrderByDescending()überhaupt nicht oder keine),

Gibt das erste Element einer Sequenz zurück

Betrachten Sie eine Handvoll Beispielabfragen. Es ist nicht immer klar, wann diese beiden Methoden verwendet werden sollen:

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?

Frage

Welche Konventionen befolgen oder schlagen Sie bei der Entscheidung für die Verwendung SingleOrDefault()und FirstOrDefault()in Ihren LINQ-Abfragen vor?

p.campbell
quelle

Antworten:

466

Wann immer Sie verwenden SingleOrDefault, geben Sie klar an, dass die Abfrage höchstens zu einem einzigen Ergebnis führen soll. Auf der anderen Seite FirstOrDefaultkann die Abfrage bei Verwendung eine beliebige Anzahl von Ergebnissen zurückgeben, Sie geben jedoch an, dass Sie nur das erste möchten.

Ich persönlich finde die Semantik sehr unterschiedlich und die Verwendung der entsprechenden Semantik, abhängig von den erwarteten Ergebnissen, verbessert die Lesbarkeit.

Bryan Menard
quelle
164
Ein sehr wichtiger Unterschied besteht darin, dass bei Verwendung von SingleOrDefault für eine Sequenz mit mehr als einem Element eine Ausnahme ausgelöst wird.
Kamran Bigdely
17
@kami Wenn es keine Ausnahme auslösen würde, wäre es genau wie FirstOrDefault. Die Ausnahme ist, was es SingleOrDefault macht. Es ist ein guter Punkt, es zur Sprache zu bringen und den Sarg der Unterschiede festzunageln.
Fabio S.
17
Ich muss sagen, dass FirstOrDefault in Bezug auf die Leistung etwa zehnmal schneller arbeitet als SingleOrDefault. Bei Verwendung einer Liste <MyClass> mit 9.000.000 Elementen enthält die Klasse 2 Ganzzahlen und die Func enthält eine Suche nach diesen beiden Ganzzahlen. Das 200-malige Suchen in einer Schleife dauerte 22 Sekunden in var v = list.SingleOrDefault (x => x.Id1 == i && x.Id2 == i); und var v = list.FirstOrDefault (x => x.Id1 == i && x.Id2 == i); ungefähr 3 Sekunden
Chen
6
@BitsandBytesHandyman Wenn SignleOrDefault keine Ausnahme auslöste, wenn die Sequenz mehr als ein Element enthielt, würde es sich nicht genau wie FirstOrDefault verhalten. FirstOrDefault gibt das erste Element zurück oder null, wenn die Sequenz leer ist. SingleOrDefault sollte das einzige Element zurückgeben oder null, wenn die Sequenz leer ist ODER wenn sie mehr als ein Element enthält, ohne überhaupt eine Ausnahme auszulösen.
Thanasis Ioannidis
2
@RSW Ja, das ist mir bewusst. Als ich meinen Kommentar sorgfältig las, sagte ich, was SingleOrDefault tun sollte, nicht was es tut. Aber was es tun sollte, ist natürlich sehr subjektiv. Für mich bedeutet das Muster "SomethingOrDefault": Holen Sie sich den Wert von "Something". Wenn "Something" keinen Wert zurückgibt, geben Sie den Standardwert zurück. Dies bedeutet, dass der Standardwert auch dann zurückgegeben werden sollte, wenn "Something" eine Ausnahme auslösen würde. Wenn Single also eine Ausnahme auslösen würde, sollte SingleOrDefault aus meiner Sicht den Standardwert zurückgeben.
Thanasis Ioannidis
585

Wenn Ihre Ergebnismenge 0 Datensätze zurückgibt:

  • SingleOrDefault Gibt den Standardwert für den Typ zurück (z. B. ist der Standardwert für int 0).
  • FirstOrDefault Gibt den Standardwert für den Typ zurück

Wenn Ihre Ergebnismenge 1 Datensatz zurückgibt:

  • SingleOrDefault gibt diesen Datensatz zurück
  • FirstOrDefault gibt diesen Datensatz zurück

Wenn Ihre Ergebnismenge viele Datensätze zurückgibt:

  • SingleOrDefault löst eine Ausnahme aus
  • FirstOrDefault gibt den ersten Datensatz zurück

Fazit:

Wenn eine Ausnahme ausgelöst werden soll, wenn die Ergebnismenge viele Datensätze enthält, verwenden Sie SingleOrDefault.

Wenn Sie immer 1 Datensatz möchten, unabhängig davon, was die Ergebnismenge enthält, verwenden Sie FirstOrDefault

Alex
quelle
6
Ich würde vorschlagen, dass es selten ist, die Ausnahme tatsächlich zu wollen, so dass FirstOrDefault die meiste Zeit bevorzugt wird. Ich weiß, dass Fälle nur nicht so oft imo existieren würden.
MikeKulls
FirstOrDefaultwird der erste Datensatz zurückgegeben bedeutet neuer Datensatz (letzter) / alter Datensatz (erster)? Kannst du mich klären?
Duk
@ Duk, es hängt davon ab, wie Sie die Datensätze sortieren. Sie können OrderBy () oder OrderByDescending () usw. verwenden, bevor Sie FirstOrDefault aufrufen. Siehe das Codebeispiel des OP.
Gan
5
Ich mag diese Antwort auch. Vor allem, wenn es einige Fälle gibt, in denen Sie die Ausnahme tatsächlich auslösen möchten, weil Sie beabsichtigen, diesen seltenen Fall an anderer Stelle richtig zu behandeln, anstatt nur so zu tun, als ob dies nicht der Fall wäre. Wenn Sie die Ausnahme wünschen, sagen Sie dies deutlich und zwingen andere dazu, nur die Umhüllung zu handhaben, wodurch das Gesamtsystem robuster wird.
Francis Rodgers
Es ist sehr klar angegeben, so dass man leicht verstehen kann.
Nirav Vasoya
243

Es gibt

  • ein semantischer Unterschied
  • ein Leistungsunterschied

zwischen den beiden.

Semantischer Unterschied:

  • FirstOrDefault Gibt ein erstes Element von möglicherweise mehreren zurück (oder Standard, wenn keines vorhanden ist).
  • SingleOrDefaultgeht davon aus, dass es ein einzelnes Element gibt, und gibt es zurück (oder standardmäßig, wenn keines vorhanden ist). Mehrere Artikel stellen eine Vertragsverletzung dar, eine Ausnahme wird ausgelöst.

Leistungsunterschied

  • FirstOrDefaultist normalerweise schneller, es iteriert, bis es das Element findet und muss nur die gesamte Aufzählung iterieren, wenn es es nicht findet. In vielen Fällen besteht eine hohe Wahrscheinlichkeit, einen Artikel zu finden.

  • SingleOrDefaultmuss prüfen, ob es nur ein Element gibt und iteriert daher immer die gesamte Aufzählung. Um genau zu sein, iteriert es, bis es ein zweites Element findet und eine Ausnahme auslöst. In den meisten Fällen gibt es jedoch kein zweites Element.

Fazit

  • Verwenden FirstOrDefaultSie diese Option, wenn es Ihnen egal ist, wie viele Artikel vorhanden sind oder wenn Sie es sich nicht leisten können, die Eindeutigkeit zu überprüfen (z. B. in einer sehr großen Sammlung). Wenn Sie beim Hinzufügen der Elemente zur Sammlung die Eindeutigkeit überprüfen, ist es möglicherweise zu teuer, sie bei der Suche nach diesen Elementen erneut zu überprüfen.

  • Verwenden SingleOrDefaultSie diese Option, wenn Sie sich nicht zu sehr um die Leistung kümmern müssen und sicherstellen möchten, dass die Annahme eines einzelnen Elements für den Leser klar ist und zur Laufzeit überprüft wird.

In der Praxis verwenden Sie First/ FirstOrDefaulthäufig sogar in Fällen, in denen Sie ein einzelnes Element annehmen, um die Leistung zu verbessern. Sie sollten sich immer noch daran erinnern, dass Single/ SingleOrDefaultdie Lesbarkeit (weil es die Annahme eines einzelnen Elements angibt) und die Stabilität (weil es es überprüft) verbessern und es angemessen verwenden kann.

Stefan Steinegger
quelle
16
+1 "oder wenn Sie es sich nicht leisten können, die Eindeutigkeit zu überprüfen (z. B. in einer sehr großen Sammlung)." . Ich habe danach gesucht. Ich würde auch hinzufügen, um die Eindeutigkeit beim Einfügen oder / und beabsichtigt zu erzwingen, anstatt zum Zeitpunkt der Abfrage!
Nawaz
Ich kann mir vorstellen, dass SingleOrDefaultbei der Verwendung von Linq to Objects viele Objekte durchlaufen werden, muss aber nicht SingleOrDefaulthöchstens zwei Elemente durchlaufen, wenn Linq beispielsweise mit einer Datenbank spricht. Ich frage mich nur ..
Memet Olsen
3
@memetolsen Betrachten Sie die Code-Spucke für die beiden mit LINQ to SQL - FirstOrDefault verwendet Top 1. SingleOrDefault verwendet Top 2.
Jim Wooley
@ JimWooley Ich glaube, ich habe das Wort "Aufzählbar" falsch verstanden. Ich dachte, Stefan meinte das C # Enumerable.
Memet Olsen
1
@memetolsen korrekt in Bezug auf die ursprüngliche Antwort, Ihr Kommentar bezog sich auf die Datenbank, also bot ich an, was vom Anbieter passiert. Während der .NET-Code nur über zwei Werte iteriert, besucht die Datenbank so viele Datensätze wie nötig, bis der zweite die Kriterien erfüllt.
Jim Wooley
76

Niemand hat erwähnt, dass FirstOrDefault, das in SQL übersetzt wurde, TOP 1-Datensätze und SingleOrDefault TOP 2-Datensätze ausführt, da bekannt sein muss, dass mehr als 1 Datensatz vorhanden ist.

Shalke
quelle
3
Als ich einen SingleOrDefault über LinqPad und VS ausgeführt habe, habe ich nie SELECT TOP 2 erhalten. Mit FirstOrDefault konnte ich SELECT TOP 1 erhalten, aber soweit ich Ihnen sagen kann, erhalten Sie SELECT TOP 2 nicht.
Jamie R Rytlewski
Hey, ich wurde auch in Linqpad ausprobiert und SQL-Abfragen haben mir Angst gemacht, weil sie alle Zeilen vollständig abrufen. Ich bin mir nicht sicher, wie das passieren kann.
AnyOne
1
Dies hängt vollständig vom verwendeten LINQ-Anbieter ab. Beispielsweise können LINQ to SQL und LINQ to Entities auf unterschiedliche Weise in SQL übersetzt werden. Ich habe gerade LINQPad mit dem IQ MySql-Anbieter ausprobiert und füge FirstOrDefault()hinzu, LIMIT 0,1während SingleOrDefault()nichts hinzugefügt wird .
Lucas
1
EF Core 2.1 übersetzt FirstOrDefault in SELECT TOP (1), SingleOrDefault in SELECT TOP (2)
camainc
19

Für LINQ -> SQL:

SingleOrDefault

  • generiert eine Abfrage wie "select * from users where userid = 1"
  • Übereinstimmenden Datensatz auswählen. Löst eine Ausnahme aus, wenn mehr als ein Datensatz gefunden wurde
  • Verwenden Sie diese Option, wenn Sie Daten basierend auf der Primär- / eindeutigen Schlüsselspalte abrufen

FirstOrDefault

  • generiert eine Abfrage wie "Top 1 * von Benutzern auswählen, bei denen Benutzer-ID = 1"
  • Wählen Sie die ersten übereinstimmenden Zeilen aus
  • Verwenden Sie diese Option, wenn Sie Daten abrufen, die auf einer nicht primären / eindeutigen Schlüsselspalte basieren
Prakash
quelle
Ich denke, Sie sollten "Alle übereinstimmenden Zeilen auswählen" von SingleOrDefault
Saim Abdullah
10

Ich verwende SingleOrDefaultin Situationen, in denen meine Logik vorschreibt, dass das Ergebnis entweder Null oder Eins ist. Wenn es mehr gibt, ist es eine Fehlersituation, die hilfreich ist.

Spender
quelle
3
Oft stelle ich fest, dass SingleOrDefault () Fälle hervorhebt, in denen ich nicht die richtige Filterung auf die Ergebnismenge angewendet habe oder bei denen ein Problem mit Duplikaten in den zugrunde liegenden Daten vorliegt. Meistens verwende ich Single () und SingleOrDefault () über die First () -Methoden.
TimS
Es gibt Auswirkungen auf die Leistung von Single () und SingleOrDefault () bei LINQ to Objects, wenn Sie eine große Aufzählung haben, aber wenn Sie mit einer Datenbank (z. B. SQL Server) sprechen, wird ein Top-2-Aufruf ausgeführt, und wenn Ihre Indizes korrekt eingerichtet sind Aufruf sollte nicht teuer sein und ich würde lieber schnell scheitern und das Datenproblem finden, anstatt möglicherweise andere Datenprobleme einzuführen, indem ich beim Aufrufen von First () oder FirstOrDefault () das falsche Duplikat nehme.
Heartlandcoder
5

SingleOrDefault: Sie sagen, dass "höchstens" ein Element mit der Abfrage oder dem Standard übereinstimmt. FirstOrDefault: Sie sagen, dass "mindestens" ein Element mit der Abfrage oder dem Standard übereinstimmt

Sagen Sie das laut, wenn Sie das nächste Mal wählen müssen, und Sie werden wahrscheinlich mit Bedacht wählen. :) :)

Steven
quelle
5
Eigentlich keine Ergebnisse zu haben, ist durchaus akzeptabel. Verwendung von FirstOrDefault. More correctly: FirstOrDefault` = Beliebig viele Ergebnisse, aber ich kümmere mich nur um das erste, möglicherweise gibt es auch keine Ergebnisse. SingleOrDefault= Es gibt 1 oder 0 Ergebnisse. Wenn mehr vorhanden sind, liegt irgendwo ein Fehler vor. First= Es gibt mindestens ein Ergebnis, und ich möchte es. Single= Es gibt genau 1 Ergebnis, nicht mehr und nicht weniger, und ich möchte dieses.
Davy8
4

In Ihren Fällen würde ich Folgendes verwenden:

Auswahl nach ID == 5: Es ist in Ordnung, SingleOrDefault hier zu verwenden, da Sie eine [oder keine] Entität erwarten. Wenn Sie mehr als eine Entität mit ID 5 haben, stimmt etwas nicht und ist definitiv eine Ausnahme wert.

Wenn Sie nach Personen suchen, deren Vorname gleich "Bobby" ist, kann es mehrere geben (möglicherweise würde ich denken). Sie sollten also weder Single noch First verwenden. Wählen Sie einfach mit der Where-Operation aus (wenn "Bobby" zu viele zurückgibt Entitäten muss der Benutzer seine Suche verfeinern oder eines der zurückgegebenen Ergebnisse auswählen.

Die Reihenfolge nach Erstellungsdatum sollte auch mit einer Where-Operation ausgeführt werden (es ist unwahrscheinlich, dass nur eine Entität vorhanden ist, die Sortierung ist nicht von großem Nutzen.). Dies bedeutet jedoch, dass ALLE Entitäten sortiert werden sollen. Wenn Sie nur EINE möchten, verwenden Sie FirstOrDefault. Single würde jedes Mal werfen, wenn Sie mehr als eine Entität haben.

Olli
quelle
3
Ich stimme dir nicht zu. Wenn Ihre Datenbank-ID ein Primärschlüssel ist, erzwingt die Datenbank bereits die Eindeutigkeit. Es ist einfach albern, den CPU-Zyklus zu verschwenden, um zu überprüfen, ob die Datenbank bei jeder Abfrage ihre Arbeit erledigt.
John Henckel
4

Beide sind die Elementoperatoren und werden verwendet, um ein einzelnes Element aus einer Sequenz auszuwählen. Aber es gibt einen kleinen Unterschied zwischen ihnen. Der Operator SingleOrDefault () würde eine Ausnahme auslösen, wenn mehr als ein Element die Bedingung erfüllt, dass FirstOrDefault () keine Ausnahme für dasselbe auslöst. Hier ist das Beispiel.

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();
Sheo Dayal Singh
quelle
2
"Es gibt einen kleinen Unterschied zwischen ihnen" - Das ist groß!
Nikhil Vartak
3

In Ihrem letzten Beispiel:

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

Ja tut es. Wenn Sie versuchen zu verwenden SingleOrDefault()und die Abfrage zu mehr als einem Datensatz führt, erhalten Sie eine Ausnahme. Die einzige Zeit, die Sie sicher verwenden können, SingleOrDefault()ist, wenn Sie nur 1 und nur 1 Ergebnis erwarten ...

Bytebender
quelle
Das ist richtig. Wenn Sie das Ergebnis 0 erhalten, erhalten Sie auch eine Ausnahme.
Dennis Rongo
1

So wie ich es jetzt verstehe, SingleOrDefaultist es gut, wenn Sie nach Daten fragen, die garantiert eindeutig sind, dh durch DB-Einschränkungen wie den Primärschlüssel erzwungen werden.

Oder gibt es eine bessere Möglichkeit, den Primärschlüssel abzufragen?

Vorausgesetzt mein TableAcc hat

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

und ich möchte nach einem fragen AccountNumber 987654, den ich benutze

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

Meiner Meinung nach FirstOrDefaultwird viel überbeansprucht. In den meisten Fällen, in denen Sie Daten filtern, erwarten Sie entweder eine Sammlung von Elementen, die der logischen Bedingung entsprechen, oder ein einzelnes eindeutiges Element anhand seiner eindeutigen Kennung - wie z. B. ein Benutzer, ein Buch, ein Beitrag usw. Warum können wir sogar sagen, dass dies FirstOrDefault()ein Codegeruch ist, nicht weil etwas nicht stimmt, sondern weil er viel zu oft verwendet wird. Dieser Blog-Beitrag befasst sich ausführlich mit dem Thema. IMO ist meistens SingleOrDefault()eine viel bessere Alternative. Achten Sie also auf diesen Fehler und stellen Sie sicher, dass Sie die am besten geeignete Methode verwenden, die Ihren Vertrag und Ihre Erwartungen klar darstellt.

Vasil Kosturski
quelle
-1

Eine Sache, die in den Antworten übersehen wird ....

Wenn es mehrere Ergebnisse gibt, kann FirstOrDefault ohne eine Bestellung von unterschiedliche Ergebnisse zurückbringen, je nachdem, welche Indexstrategie gerade vom Server verwendet wurde.

Persönlich kann ich es nicht ertragen, FirstOrDefault im Code zu sehen, weil es mir sagt, dass der Entwickler sich nicht um die Ergebnisse gekümmert hat. Bei einer Bestellung von kann dies jedoch hilfreich sein, um die neuesten / frühesten zu erzwingen. Ich musste viele Probleme beheben, die von unachtsamen Entwicklern mit FirstOrDefault verursacht wurden.

El Dude
quelle
-2

Ich habe Google nach der Verwendung der verschiedenen Methoden auf GitHub gefragt. Dazu wird für jede Methode eine Google-Suchabfrage ausgeführt und die Abfrage mithilfe der Abfrage "site: github.com file: cs ..." auf die Domain github.com und die Dateierweiterung .cs beschränkt.

Es scheint, dass die First * -Methoden häufiger verwendet werden als die Single * -Methoden.

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

Ich verstehe nicht, warum Sie verwenden, FirstOrDefault(x=> x.ID == key)wenn dies bei Verwendung viel schneller zu Ergebnissen führen könnte Find(key). Wenn Sie mit dem Primärschlüssel der Tabelle abfragen, gilt als Faustregel, immer zu verwenden Find(key). FirstOrDefaultsollte für Prädikate wie (x=> x.Username == username)etc. verwendet werden.

Dies hatte keine Abwertung verdient, da die Überschrift der Frage nicht spezifisch für linq on DB oder Linq to List / IEnumerable usw. war.

Theron Govender
quelle
1
In welchem ​​Namespace befindet sich Find()?
p.campbell
Könnten Sie uns bitte sagen? Ich warte immer noch auf eine Antwort.
Denny
Könnte dies sein: stackoverflow.com/questions/14032709/…
Jeppe
Das Wort "IEnumerable" steht in der allerersten Zeile des Fragentextes. Wenn Sie nur den Titel und nicht die eigentliche Frage lesen und als Ergebnis eine falsche Antwort veröffentlichen, ist dies Ihr Fehler und ein absolut legitimer Grund, IMO abzulehnen.
F1Krazy