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

Considerare i metodi di estensione IEnumerable SingleOrDefault() e FirstOrDefault()

MSDN documenta SingleOrDefault :

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

considerando che FirstOrDefault da MSDN (presumibilmente quando si utilizza un OrderBy() o OrderByDescending() o nessuno affatto),

Restituisce il primo elemento di una sequenza

Considerare 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 SingleOrDefault() utilizzare SingleOrDefault() e FirstOrDefault() nelle tue query LINQ?

Ogni volta che utilizzi SingleOrDefault , SingleOrDefault chiaramente che la query dovrebbe comportare al massimo un singolo risultato. D’altra parte, quando viene utilizzato FirstOrDefault , la query può restituire qualsiasi quantità di risultati, ma si afferma che si desidera solo il primo.

Personalmente trovo la semantica molto diversa e uso quella appropriata, a seconda dei risultati attesi, migliora la leggibilità.

Se il tuo 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 si imposta set restituisce 1 record:

  • SingleOrDefault restituisce quel record
  • FirstOrDefault restituisce quel record

Se il tuo 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

C’è

  • una differenza semantica
  • una differenza di prestazioni

tra i due.

Differenza semantica:

  • FirstOrDefault restituisce un primo elemento potenzialmente multiplo (o predefinito se nessuno esiste).
  • SingleOrDefault presuppone che ci sia un singolo object e lo restituisce (o predefinito se nessuno esiste). Più articoli sono una violazione del contratto, viene generata un’eccezione.

Differenza di prestazioni

  • FirstOrDefault è solitamente più veloce, itera fino a quando non trova l’elemento e deve solo iterare l’intero enumerable quando non lo trova. In molti casi, c’è un’alta probabilità di trovare un object.

  • SingleOrDefault deve verificare se esiste un solo elemento e quindi itera sempre l’intero enumerabile. Per essere precisi, itera fino a quando non trova un secondo elemento e lancia un’eccezione. Ma nella maggior parte dei casi, non c’è un secondo elemento.

Conclusione

  • Usa FirstOrDefault se non ti interessa quanti articoli ci sono o quando non ti puoi permettere di controllare l’unicità (ad esempio in una collezione molto grande). Quando si controlla l’univocità aggiungendo gli elementi alla raccolta, potrebbe essere troppo costoso controllarlo di nuovo durante la ricerca di tali elementi.

  • Usa SingleOrDefault se non ti interessa troppo le prestazioni e vuoi assicurarti che l’assunzione di un singolo elemento sia chiara per il lettore e verificata in fase di runtime.

In pratica, si utilizza First / FirstOrDefault spesso anche nei casi in cui si presuppone un singolo elemento, per migliorare le prestazioni. Dovresti comunque ricordare che Single / SingleOrDefault può migliorare la leggibilità (perché indica l’assunzione di un singolo elemento) e la stabilità (perché lo controlla) e usarlo in modo appropriato.

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

Per LINQ -> SQL:

SingleOrDefault

  • genererà query come “select * from users where userid = 1”
  • Seleziona record corrispondente, genera un’eccezione se vengono trovati più 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 basati su una colonna chiave non primaria / univoca

Io uso SingleOrDefault in situazioni in cui la mia logica impone che sia zero o un risultato. Se ce ne sono altri, è una situazione di errore, che è utile.

SingleOrDefault: stai dicendo che “Al massimo” c’è un elemento che corrisponde alla query o predefinito FirstOrDefault: stai dicendo che c’è “Almeno” un elemento corrispondente alla query o predefinito

Dillo ad alta voce la prossima volta che devi scegliere e probabilmente sceglierai saggiamente. 🙂

Nei tuoi casi, vorrei usare il seguente:

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

quando si cercano persone il cui nome è uguale a “Bobby”, ce ne possono essere più di uno (molto probabilmente ci penserei), quindi non si dovrebbe usare né Single né First, basta selezionare con l’operazione Where (se “Bobby” restituisce troppe quadro, l’utente deve affinare la sua ricerca o scegliere uno dei risultati restituiti)

l’ordine per data di creazione dovrebbe anche essere eseguito con un’operazione Where (difficilmente avrà solo un’entity framework, l’ordinamento non sarebbe di grande utilità;) questo tuttavia implica che TUTTE le quadro siano ordinate – se si desidera UNO, utilizzare FirstOrDefault, La singola getterebbe ogni volta se hai più di un’ quadro.

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 in più del record si otterrebbe ed eccezione. L’unica volta che puoi tranquillamente usare SingleOrDefault() è quando ti aspetti solo 1 e solo 1 risultato …

Entrambi sono gli operatori dell’elemento e sono usati 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 della condizione in cui FirstOrDefault () non genererà alcuna eccezione per lo stesso. Ecco l’esempio.

 List items = new List() {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(); 

Quindi, come capisco ora, SingleOrDefault sarà buono se stai interrogando per i dati che sono garantiti come univoci, cioè applicati da vincoli di DB come la chiave primaria.

O c’è un modo migliore di interrogare per la chiave primaria.

Supponendo che il mio TableAcc abbia

 AccountNumber - Primary Key, integer AccountName AccountOpenedDate AccountIsActive etc. 

e voglio richiedere un AccountNumber 987654 , io lo uso

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

Una cosa che è mancata nelle risposte ….

Se ci sono più risultati, FirstOrDefault senza un ordine può riportare risultati diversi in base a quale strategia di indice è mai stata utilizzata dal server.

Personalmente non sopporto di vedere FirstOrDefault nel codice perché per me dice che lo sviluppatore non si è preoccupato dei risultati. Con un ordine, tuttavia, può essere utile come modo per far rispettare l’ultima / prima. Ho dovuto correggere un sacco di problemi causati da sviluppatori incuranti che usano FirstOrDefault.

Non capisco perché stai usando FirstOrDefault(x=> x.ID == key) quando questo potrebbe recuperare i risultati molto più velocemente se usi Find(key) . Se stai interrogando con la chiave primaria della tabella, la regola empirica è di usare sempre Find(key) . FirstOrDefault dovrebbe essere usato per roba di predicato 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 su List / IEnumerable ecc.