Risposte:
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 Customers
in 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 Customer
uso 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.
customers.Where(predicate).Single()
customers.Single(predicate)
?
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 InvalidOperationException
un'eccezione se la raccolta è vuota. In alternativa puoi usare SingleOrDefault()
. Ciò non genererà un'eccezione se l'elenco è vuoto
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 casolist<string>
, restituirà la prima stringa dall'elenco o null se l'elenco è vuoto.
First
quando sono previsti 1 o più elementi , non solo "più di 1" e FirstOrDefault
con qualsiasi quantità di elementi.
C'è una sottile differenza semantica tra questi due metodi.
Utilizzare Single
per 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 Single
provocherà un'eccezione poiché hai indicato che dovrebbe esserci un solo elemento.
Utilizzare First
per 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 First
non 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.
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.
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 ().
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.
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.
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);
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 =1
perché abbiamo un solo Dipendente di cui Employeed
è 1. Se abbiamo due record per EmployeeId = 1
allora 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 = 10
restituirà 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 birthdate
quale 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.