LINQ Single vs First


216

LINQ:

È più efficiente utilizzare l' Single()operatore First()quando mai so con certezza che la query restituirà un singolo record ?

C'è una differenza?

Risposte:


312

Se ti aspetti un singolo record, è sempre bene essere espliciti nel tuo codice.

So che altri hanno scritto perché usi l'uno o l'altro, ma ho pensato di illustrare perché NON dovresti usare l'uno, quando intendi l'altro.

Nota: Nel mio codice, io di solito uso FirstOrDefault()e SingleOrDefault()ma questa è una questione diversa.

Prendi, ad esempio, una tabella che memorizza Customersin diverse lingue usando una chiave composita ( ID, Lang):

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

Questo codice sopra introduce un possibile errore logico (difficile da rintracciare). Restituirà più di un record (supponendo che tu abbia il record del cliente in più lingue) ma restituirà sempre solo il primo ... che a volte può funzionare ... ma non altri. È imprevedibile.

Poiché il tuo intento è di restituire un Customeruso singolo Single();

Quanto segue genererebbe un'eccezione (che è quello che vuoi in questo caso):

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

Quindi, ti colpisci semplicemente sulla fronte e dici a te stesso ... OOPS! Ho dimenticato il campo della lingua! Di seguito è la versione corretta:

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

First() è utile nel seguente scenario:

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

Restituirà UN oggetto e, poiché stai usando l'ordinamento, sarà il record più recente che verrà restituito.

L'uso Single()quando ritieni che debba sempre esplicitamente restituire 1 record ti aiuterà a evitare errori logici.


76
Entrambi i metodi Single e First possono accettare un parametro di espressione per il filtro, quindi la funzione Where non è necessaria. Esempio: cliente cliente = db.Customers.Single (c => c.ID == 5);
Josh Noe,

6
@JoshNoe - Sono curioso, c'è una differenza tra customers.Where(predicate).Single() customers.Single(predicate)?
drzaus,

9
@drzaus: logicamente no, entrambi filtrano i valori da restituire in base al predicato. Tuttavia, ho verificato lo smontaggio e Where (predicato) .Single () ha tre istruzioni aggiuntive nel caso semplice che ho creato. Quindi, mentre io non sono un esperto di IL, ma sembra che i clienti. Singolo (predicato) dovrebbe essere più efficiente.
Josh Noe,

5
@JoshNoe è piuttosto un contrario, a quanto pare.
AgentFire


72

Single genererà un'eccezione se trova più di un record corrispondente ai criteri. In primo luogo selezionerà sempre il primo record dall'elenco. Se la query restituisce solo 1 record, puoi procedere con First().

Entrambi genereranno InvalidOperationExceptionun'eccezione se la raccolta è vuota. In alternativa puoi usare SingleOrDefault(). Ciò non genererà un'eccezione se l'elenco è vuoto


30

Singolo ()

Restituisce un singolo elemento specifico di una query

Quando utilizzato : se è previsto esattamente 1 elemento; non 0 o più di 1. Se l'elenco è vuoto o contiene più di un elemento, verrà generata un'eccezione "La sequenza contiene più di un elemento"

SingleOrDefault ()

Restituisce un singolo elemento specifico di una query o un valore predefinito se non viene trovato alcun risultato

Quando utilizzato : quando sono previsti 0 o 1 elementi. Genererà un'eccezione se l'elenco contiene 2 o più elementi.

Primo()

Restituisce il primo elemento di una query con più risultati.

Quando utilizzato : quando sono previsti 1 o più elementi e si desidera solo il primo. Genererà un'eccezione se l'elenco non contiene elementi.

FirstOrDefault ()

Restituisce il primo elemento di un elenco con qualsiasi quantità di elementi o un valore predefinito se l'elenco è vuoto.

Quando utilizzato : quando sono previsti più elementi e si desidera solo il primo. Oppure l'elenco è vuoto e si desidera un valore predefinito per il tipo specificato, lo stesso di default(MyObjectType). Ad esempio: se il tipo di elenco è list<int>, verrà restituito il primo numero dall'elenco o 0 se l'elenco è vuoto. In tal caso list<string>, restituirà la prima stringa dall'elenco o null se l'elenco è vuoto.


1
Bella spiegazione. Vorrei solo cambiare che puoi usare Firstquando sono previsti 1 o più elementi , non solo "più di 1" e FirstOrDefaultcon qualsiasi quantità di elementi.
Andrew,

18

C'è una sottile differenza semantica tra questi due metodi.

Utilizzare Singleper recuperare il primo (e unico) elemento da una sequenza che dovrebbe contenere un elemento e non più. Se la sequenza ha più di un elemento, la tua invocazione Singleprovocherà un'eccezione poiché hai indicato che dovrebbe esserci un solo elemento.

Utilizzare Firstper recuperare il primo elemento da una sequenza che può contenere qualsiasi numero di elementi. Se la sequenza ha più di un elemento, la tua invocazione Firstnon provocherà un'eccezione poiché hai indicato che hai solo bisogno del primo elemento nella sequenza e non ti importa se ne esistono di più.

Se la sequenza non contiene elementi, entrambe le chiamate al metodo genereranno eccezioni poiché entrambi i metodi prevedono che sia presente almeno un elemento.


18

Se non si desidera che venga generata un'eccezione nel caso in cui sia presente più di un elemento, utilizzareFirst() .

Entrambi sono efficienti, prendi il primo oggetto. First()è leggermente più efficiente perché non si preoccupa di verificare se esiste un secondo elemento.

L'unica differenza è che si Single()aspetta che ci sia un solo elemento nell'enumerazione per iniziare e genererà un'eccezione se ce ne sono più di uno. Si utilizza .Single() se si desidera specificamente generare un'eccezione in questo caso.


14

Se ricordo, Single () controlla se c'è un altro elemento dopo il primo (e genera un'eccezione se è il caso), mentre First () si ferma dopo averlo ottenuto. Entrambi generano un'eccezione se la sequenza è vuota.

Personalmente, uso sempre First ().


2
Nell'SQL producono First () fa TOP 1 e Single () fa TOP 2 se non sbaglio.
Matthijs Wessels,

10

Per quanto riguarda le prestazioni: un collega e io stavamo discutendo delle prestazioni di Single vs First (o SingleOrDefault vs FirstOrDefault), e stavo discutendo del fatto che First (o FirstOrDefault) sarebbe stato più veloce e migliorato le prestazioni (sono tutto sulla creazione della nostra app correre più veloce).

Ho letto diversi post su Stack Overflow che discutono di questo. Alcuni dicono che ci sono piccoli miglioramenti delle prestazioni usando First invece di Single. Questo perché First restituisce semplicemente il primo elemento mentre Single deve eseguire la scansione di tutti i risultati per assicurarsi che non vi sia un duplicato (ovvero: se trovasse l'elemento nella prima riga della tabella, eseguirà comunque la scansione di ogni altra riga su assicurarsi che non vi sia un secondo valore corrispondente alla condizione che genererebbe un errore). Mi sentivo come se fossi su un terreno solido con "First" più veloce di "Single", quindi ho deciso di dimostrarlo e di porre fine al dibattito.

Ho impostato un test nel mio database e ho aggiunto 1.000.000 di righe di ID UniqueIdentifier Foreign UniqueIdentifier Info nvarchar (50) (riempito con stringhe di numeri da "0" a "999.9999"

Ho caricato i dati e impostato l'ID come campo chiave principale.

Usando LinqPad, il mio obiettivo era mostrare che se avessi cercato un valore su "Foreign" o "Info" usando Single, sarebbe stato molto peggio che usare First.

Non riesco a spiegare i risultati che ho ottenuto. In quasi tutti i casi, l'utilizzo di Single o SingleOrDefault è stato leggermente più veloce. Questo non ha alcun senso logico per me, ma volevo condividerlo.

Es: ho usato le seguenti query:

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

Ho provato query simili sul campo chiave "Estero" che non è stato indicizzato pensando che prima sarebbe più veloce, ma Single è stato sempre leggermente più veloce nei miei test.


2
Se si trova su un campo indicizzato rispetto al database, non è necessario eseguire una scansione per assicurarsi che sia univoco. Sa già che lo è. Quindi non c'è sovraccarico lì, e l'unico sovraccarico sul lato server è garantire che venga restituito solo un record. Fare i test delle prestazioni da solo non è conclusivo in un modo o nell'altro
mirhagk il

1
Non penso che questi risultati sarebbero gli stessi su un oggetto complesso se si stesse cercando in un campo non utilizzato da IComparer.
Anthony Nichols,

Questa dovrebbe essere un'altra domanda. Assicurati di includere l'origine del test .
Sinatr,

5

Sono diversi. Entrambi affermano che il set di risultati non è vuoto, ma single afferma anche che non c'è più di 1 risultato. Personalmente uso Single nei casi in cui mi aspetto solo un risultato solo perché ottenere più di 1 risultato è un errore e probabilmente dovrebbe essere trattato come tale.


5

Puoi provare un semplice esempio per ottenere la differenza. L'eccezione verrà generata sulla riga 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

Molte persone che conosco usano FirstOrDefault (), ma tendo a usare SingleOrDefault () più spesso perché spesso sarebbe una sorta di incoerenza dei dati se ce ne fossero più di una. Tuttavia, si tratta di LINQ-to-Objects.


-1

I record nell'entità Dipendente:

Employeeid = 1: Un solo dipendente con questo ID

Firstname = Robert: Più di un dipendente con questo nome

Employeeid = 10: Nessun dipendente con questo ID

Ora è necessario capire cosa Single()e cosa First()significa in dettaglio.

Singolo ()

Single () viene utilizzato per restituire un singolo record che esiste in modo univoco in una tabella, quindi la query riportata di seguito restituirà il Dipendente employeed =1perché abbiamo un solo Dipendente di cui Employeedè 1. Se abbiamo due record per EmployeeId = 1allora genera un errore (vedere il errore di seguito nella seconda query in cui stiamo usando un esempio per Firstname.

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

Quanto sopra restituirà un singolo record, che ha 1 employeeId

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

Quanto sopra genererà un'eccezione perché i record multilple sono nella tabella per FirstName='Robert'. L'eccezione sarà

InvalidOperationException: la sequenza contiene più di un elemento

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

Questo, di nuovo, genererà un'eccezione perché non esiste alcun record per id = 10. L'eccezione sarà

InvalidOperationException: la sequenza non contiene elementi.

Perché EmployeeId = 10restituirà null, ma mentre lo stiamo usando Single()genererà un errore. Al fine di gestire l'errore null dovremmo usare SingleOrDefault().

Primo()

First () restituisce da più record i record corrispondenti ordinati in ordine crescente secondo il birthdatequale restituirà "Robert" che è il più vecchio.

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

Sopra dovrebbe restituire il più vecchio, Robert come da DOB.

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

Sopra genererà un'eccezione poiché non esiste alcun record per id = 10. Per evitare un'eccezione nulla dovremmo usare FirstOrDefault()piuttosto che First().

Nota: possiamo usare solo First()/ Single()quando siamo assolutamente sicuri che non possa restituire un valore nullo.

In entrambe le funzioni utilizzare SingleOrDefault () OR FirstOrDefault () che gestirà un'eccezione nulla, nel caso in cui non venga trovato alcun record restituirà null.


Per favore, spiega la tua risposta.
Mr Maavin,

1
@MrMaavin Ho aggiornato, gentilmente fammi sapere è comprensibile ora per te?
Sceriffo
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.