LINQ: quando utilizzare SingleOrDefault vs. FirstOrDefault () con criteri di filtro


506

Considerare i metodi di estensione IEnumerable SingleOrDefault()eFirstOrDefault()

MSDN documenta cheSingleOrDefault :

Restituisce l'unico elemento di una sequenza o un valore predefinito se la sequenza è vuota; questo metodo genera un'eccezione se nella sequenza è presente più di un elemento.

che FirstOrDefaultda MSDN (presumibilmente quando si utilizza un OrderBy()o OrderByDescending()o nessuno),

Restituisce il primo elemento di una sequenza

Considera una manciata di query di esempio, non è sempre chiaro quando utilizzare questi due metodi:

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?

Domanda

Quali convenzioni segui o suggerisci quando decidi di utilizzare SingleOrDefault()e FirstOrDefault()nelle tue domande LINQ?

Risposte:


466

Ogni volta che lo usi SingleOrDefault, dichiari chiaramente che la query dovrebbe generare al massimo un singolo risultato. D'altra parte, quando FirstOrDefaultviene utilizzata, la query può restituire qualsiasi quantità di risultati ma si afferma che si desidera solo il primo.

Personalmente trovo la semantica molto diversa e l'utilizzo di quella appropriata, a seconda dei risultati attesi, migliora la leggibilità.


164
Una differenza molto importante è che se si utilizza SingleOrDefault su una sequenza con più di un elemento, viene generata un'eccezione.
Kamran Bigdely,

17
@kami se non generasse un'eccezione sarebbe esattamente come FirstOrDefault. L'eccezione è ciò che lo rende SingleOrDefault. Buon punto sollevandolo, e mette un chiodo sulla bara delle differenze.
Fabio S.

17
Devo dire che dal punto di vista delle prestazioni, FirstOrDefault funziona circa 10 volte più veloce di SingleOrDefault, usando un List <MyClass> di 9.000.000 di elementi, la classe contiene 2 numeri interi e Func contiene una ricerca di questi due numeri interi. La ricerca di 200 volte in un ciclo ha richiesto 22 secondi su var v = list.SingleOrDefault (x => x.Id1 == i && x.Id2 == i); e var v = list.FirstOrDefault (x => x.Id1 == i && x.Id2 == i); circa 3 secondi
Chen

6
@BitsandBytesHandyman Se SignleOrDefault non generasse un'eccezione quando la sequenza conteneva più di un elemento, non si comporterebbe esattamente come FirstOrDefault. FirstOrDefault restituisce il primo elemento o null se la sequenza è vuota. SingleOrDefault dovrebbe restituire l'unico elemento, oppure null se la sequenza è vuota OPPURE se contiene più di un elemento, senza generare alcuna eccezione.
Thanasis Ioannidis

2
@RSW Sì, ne sono consapevole. Leggendo attentamente il mio commento, stavo dicendo cosa dovrebbe fare SingleOrDefault, non quello che fa. Ma ovviamente, ciò che dovrebbe fare, è molto soggettivo. Per me, il modello "SomethingOrDefault" significa: Ottieni il valore di "Something". Se "Something" non riesce a restituire un valore, restituisce il valore predefinito. Ciò significa che il valore predefinito dovrebbe essere restituito anche nel caso in cui "Something" genererebbe un'eccezione. Quindi, dove Single genererebbe un'eccezione, SingleOrDefault dovrebbe restituire il valore predefinito, nella mia prospettiva.
Thanasis Ioannidis,

585

Se il set di risultati restituisce 0 record:

  • SingleOrDefault restituisce il valore predefinito per il tipo (ad es. il valore predefinito per int è 0)
  • FirstOrDefault restituisce il valore predefinito per il tipo

Se il set di risultati restituisce 1 record:

  • SingleOrDefault restituisce quel record
  • FirstOrDefault restituisce quel record

Se il set di risultati restituisce molti record:

  • SingleOrDefault genera un'eccezione
  • FirstOrDefault restituisce il primo record

Conclusione:

Se si desidera generare un'eccezione se il set di risultati contiene molti record, utilizzare SingleOrDefault.

Se si desidera sempre 1 record, indipendentemente dal contenuto del set di risultati, utilizzare FirstOrDefault


6
Suggerirei che è raro desiderare davvero l'eccezione, quindi la maggior parte delle volte si preferirebbe FirstOrDefault. So che i casi esisterebbero non così spesso.
MikeKulls,

FirstOrDefaultviene restituito il primo record significa nuovo record (ultimo) / vecchio record (primo)? mi potete chiarire?
Duk,

@Duk, dipende da come ordini i record. È possibile utilizzare OrderBy () o OrderByDescending () ecc prima di chiamare FirstOrDefault. Vedi l'esempio di codice del PO.
Gan,

5
Mi piace anche questa risposta. Soprattutto dato che ci sono alcune occasioni in cui si desidera effettivamente generare l'eccezione perché si intende gestire quel raro caso in modo adeguato altrove invece di fingere che non accada. Quando vuoi l'eccezione, lo dici chiaramente, e anche costringendo gli altri a gestire semplicemente il sistema globale, rendendolo più robusto.
Francis Rodgers,

È affermato molto chiaramente in modo che si possa facilmente capire.
Nirav Vasoya,

244

C'è

  • una differenza semantica
  • una differenza di prestazioni

tra i due.

Differenza semantica:

  • FirstOrDefault restituisce un primo elemento potenzialmente multiplo (o predefinito se non esiste).
  • SingleOrDefaultpresuppone che esista un singolo elemento e lo restituisce (o predefinito se non esiste). Più articoli sono una violazione del contratto, viene generata un'eccezione.

Differenza di prestazione

  • FirstOrDefaultdi solito è più veloce, scorre fino a quando non trova l'elemento e deve iterare l'intero enumerabile solo quando non lo trova. In molti casi, esiste un'alta probabilità di trovare un oggetto.

  • SingleOrDefaultdeve verificare se esiste un solo elemento e quindi itera sempre l'intero enumerabile. Per essere precisi, scorre fino a quando non trova un secondo elemento e genera un'eccezione. Ma nella maggior parte dei casi, non esiste un secondo elemento.

Conclusione

  • Utilizzare FirstOrDefault, se non vi interessa quanti articoli ci sono o se non può permettersi l'unicità di controllo (ad esempio, in una collezione molto grande). Quando si verifica l'univocità nell'aggiungere gli articoli alla raccolta, potrebbe essere troppo costoso ricontrollarli durante la ricerca di quegli articoli.

  • Utilizzare SingleOrDefaultse non ci si deve preoccupare troppo delle prestazioni e si desidera assicurarsi che l'assunzione di un singolo elemento sia chiara al lettore e verificata in fase di esecuzione.

In pratica, usi First/ FirstOrDefaultspesso anche nei casi in cui assumi un singolo oggetto, per migliorare le prestazioni. Dovresti ancora ricordare che Single/ SingleOrDefaultpuò migliorare la leggibilità (perché afferma l'assunzione di un singolo elemento) e la stabilità (perché lo controlla) e usarlo in modo appropriato.


16
+1 "o quando non puoi permetterti di controllare l'unicità (ad esempio in una collezione molto grande)." . Lo stavo cercando. Vorrei anche aggiungere imporre unicità durante l'inserimento, o / e in base alla progettazione anziché al momento della creazione della query!
Nawaz,

Posso immaginare di SingleOrDefaultscorrere molti oggetti quando si utilizza Linq to Objects, ma non è SingleOrDefaultnecessario scorrere al massimo 2 elementi se Linq sta parlando con un database, ad esempio? Mi chiedo solo ...
Memet Olsen,

3
@memetolsen Considera lo sputo di codice per i due con LINQ to SQL - FirstOrDefault utilizza la Top 1. SingleOrDefault utilizza la Top 2.
Jim Wooley

@JimWooley Immagino di aver frainteso la parola "enumerabile". Pensavo che Stefan intendesse il C # Enumerable.
Memet Olsen,

1
@memetolsen corretto in termini di risposta originale, il tuo commento si riferiva al database, quindi stavo offrendo ciò che accade dal fornitore. Mentre il codice .Net scorre solo 2 valori, il database visita tutti i record necessari fino a quando non raggiunge il secondo che soddisfa i criteri.
Jim Wooley,

76

Nessuno ha menzionato il fatto che FirstOrDefault tradotto in SQL fa il record TOP 1 e SingleOrDefault fa il TOP 2, perché deve sapere che c'è più di 1 record.


3
Quando ho eseguito un SingleOrDefault attraverso LinqPad e VS, non ho mai avuto SELECT TOP 2, con FirstOrDefault sono stato in grado di ottenere SELECT TOP 1, ma per quanto posso dire non ottenere SELECT TOP 2.
Jamie R Rytlewski

Ehi, sono stato provato anche in linqpad e la query sql mi ha fatto paura perché recuperava completamente tutte le righe. Non sono sicuro di come possa accadere?
AnyOne,

1
Questo dipende interamente dal provider LINQ utilizzato. Ad esempio, LINQ to SQL e LINQ to Entities potrebbero tradurre in SQL in diversi modi. Ho appena provato LINQPad con il provider IQ MySql e FirstOrDefault()aggiunge LIMIT 0,1mentre SingleOrDefault()non aggiunge nulla.
Lucas,

1
EF Core 2.1 traduce FirstOrDefault in SELECT TOP (1), SingleOrDefault in SELECT TOP (2)
camainc

19

Per LINQ -> SQL:

SingleOrDefault

  • genererà query come "select * dagli utenti dove userid = 1"
  • Seleziona record corrispondente, genera un'eccezione se viene trovato più di un record
  • Utilizzare se si stanno recuperando dati in base alla colonna chiave primaria / univoca

FirstOrDefault

  • genererà query come "seleziona top 1 * dagli utenti dove userid = 1"
  • Seleziona le prime righe corrispondenti
  • Utilizzare se si stanno recuperando dati in base alla colonna chiave non primaria / univoca

penso che dovresti rimuovere "Seleziona tutte le righe corrispondenti" da SingleOrDefault
Saim Abdullah

10

Uso SingleOrDefaultin situazioni in cui la mia logica impone che il risultato sia zero o uno. Se ce ne sono altri, è una situazione di errore, che è utile.


3
Spesso trovo che SingleOrDefault () evidenzi i casi in cui non ho applicato il filtro corretto sul set di risultati o dove c'è un problema con le duplicazioni nei dati sottostanti. Il più delle volte mi ritrovo a usare Single () e SingleOrDefault () sui metodi First ().
TimS,

Ci sono implicazioni in termini di prestazioni per Single () e SingleOrDefault () su LINQ to Objects se si dispone di un grande enumerabile ma quando si parla con un database (ad es. SQL Server), verrà eseguita una delle prime 2 chiamate e se gli indici sono impostati correttamente la chiamata non dovrebbe essere costosa e preferirei fallire rapidamente e trovare il problema dei dati invece di introdurre eventualmente altri problemi di dati prendendo il duplicato sbagliato quando si chiama First () o FirstOrDefault ().
heartlandcoder

5

SingleOrDefault: stai dicendo che "Al massimo" esiste un elemento corrispondente alla query o predefinito FirstOrDefault: Stai dicendo che c'è "Almeno" un elemento corrispondente alla query o predefinita

Dillo ad alta voce la prossima volta che dovrai scegliere e probabilmente dovrai scegliere saggiamente. :)


5
In realtà non avere risultati è un uso perfettamente accettabile di FirstOrDefault. More correctly: FirstOrDefault` = Qualsiasi numero di risultati, ma mi interessa solo il primo, potrebbero anche non esserci risultati. SingleOrDefault= Ci sono 1 o 0 risultati, se ce ne sono altri significa che c'è un errore da qualche parte. First= C'è almeno un risultato e lo voglio. Single= C'è esattamente 1 risultato, niente di più, niente di meno, e voglio quello.
Davy8,

4

Nei tuoi casi, utilizzerei quanto segue:

seleziona per ID == 5: è OK usare SingleOrDefault qui, perché ti aspetti un'entità [o nessuna], se hai più di un'entità con ID 5, c'è qualcosa di sbagliato e sicuramente un'eccezione degna.

durante la ricerca di persone il cui nome è uguale a "Bobby", possono essercene più di uno (probabilmente penserei), quindi non dovresti usare Single né First, basta selezionare con l'operazione Where (se "Bobby" restituisce troppi entità, l'utente deve perfezionare la sua ricerca o scegliere uno dei risultati restituiti)

l'ordine per data di creazione dovrebbe essere eseguito anche con un'operazione Where (è improbabile che abbia una sola entità, l'ordinamento non sarebbe di grande utilità;) ciò tuttavia implica che desideri TUTTE le entità ordinate - se vuoi solo UNO, usa FirstOrDefault, Single lancerebbe ogni volta se avessi più di un'entità.


3
Non sono d'accordo. Se l'ID del database è una chiave primaria, il database sta già applicando l'univocità. Sprecare il ciclo della CPU per verificare se il database sta facendo il suo lavoro su ogni query è semplicemente stupido.
John Henckel,

4

Entrambi sono gli operatori di elementi e vengono utilizzati per selezionare un singolo elemento da una sequenza. Ma c'è una piccola differenza tra loro. L'operatore SingleOrDefault () genererebbe un'eccezione se più di un elemento è soddisfatto, a condizione che FirstOrDefault () non genererà alcuna eccezione per lo stesso. Ecco l'esempio.

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
"c'è una piccola differenza tra loro" - È importante!
Nikhil Vartak,

3

Nel tuo ultimo esempio:

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

Sì lo fa. Se si tenta di utilizzare SingleOrDefault()e la query risulta più che record, si otterrebbe un'eccezione. L'unica volta che puoi usare in sicurezza SingleOrDefault()è quando ti aspetti solo 1 e solo 1 risultato ...


È corretto. Se ottieni 0 risultati, ricevi anche un'eccezione.
Dennis Rongo,

1

Quindi, come ho capito ora, SingleOrDefault andrà bene se si esegue una query per dati che sono garantiti come unici, vale a dire applicati da vincoli DB come la chiave primaria.

Oppure esiste un modo migliore per eseguire query per la chiave primaria.

Supponendo che il mio TableAcc abbia

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

e voglio chiedere un AccountNumber 987654, io uso

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

1

Secondo me FirstOrDefaultviene abusato molto. Nella maggior parte dei casi, quando si filtrano i dati, ci si aspetterebbe di recuperare una raccolta di elementi corrispondenti alla condizione logica o un singolo elemento univoco dal suo identificatore univoco, ad esempio un utente, un libro, un post, ecc. perché possiamo persino arrivare a dire che FirstOrDefault()è un odore di codice non perché c'è qualcosa che non va, ma perché viene usato troppo spesso. Questo post sul blog esplora l'argomento in dettaglio. IMO la maggior parte delle volte SingleOrDefault()è un'alternativa molto migliore, quindi fai attenzione a questo errore e assicurati di utilizzare il metodo più appropriato che rappresenti chiaramente il tuo contratto e le tue aspettative.


-1

Una cosa che manca nelle risposte ....

Se ci sono più risultati, FirstOrDefault senza un ordine può riportare risultati diversi in base alla strategia di indicizzazione mai utilizzata dal server.

Personalmente non sopporto di vedere FirstOrDefault nel codice perché secondo me lo sviluppatore non si preoccupava dei risultati. Con un ordine, sebbene possa essere utile come modo per far rispettare l'ultimo / il più presto. Ho dovuto correggere molti problemi causati da sviluppatori negligenti utilizzando FirstOrDefault.


-2

Ho chiesto a Google di utilizzare i diversi metodi su GitHub. Questo viene fatto eseguendo una query di ricerca di Google per ciascun metodo e limitando la query al dominio github.com e l'estensione del file .cs utilizzando la query "site: file github.com: cs ..."

Sembra che i metodi First * siano più comunemente usati rispetto ai metodi Single *.

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

-8

Non capisco perché stai usando FirstOrDefault(x=> x.ID == key)quando questo potrebbe recuperare risultati molto più velocemente se lo usi Find(key). Se si esegue una query con la chiave primaria della tabella, la regola empirica è di utilizzare sempre Find(key). FirstOrDefaultdovrebbe essere usato per cose predicate come (x=> x.Username == username)ecc.

questo non meritava un downvote in quanto l'intestazione della domanda non era specifica per linq su DB o Linq to List / IEnumerable ecc.


1
In quale spazio dei nomi si trova Find()?
p.campbell

Potresti dircelo, per favore? Sto ancora aspettando una risposta.
Denny


La parola "IEnumerable" è nella prima riga del corpo della domanda. Se leggi solo il titolo e non la vera domanda e, di conseguenza, hai pubblicato una risposta errata, questo è il tuo errore e un motivo perfettamente legittimo per declassare l'IMO.
F1Krazy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.