LINQ: quand utiliser SingleOrDefault vs FirstOrDefault () avec des critères de filtrage

506

Considérez les méthodes d'extension IEnumerable SingleOrDefault()etFirstOrDefault()

Documents MSDN quiSingleOrDefault :

Renvoie le seul élément d'une séquence ou une valeur par défaut si la séquence est vide; cette méthode lève une exception s'il y a plus d'un élément dans la séquence.

tandis que FirstOrDefaultde MSDN (probablement lors de l' utilisation d' un OrderBy()ou OrderByDescending()ou pas du tout),

Renvoie le premier élément d'une séquence

Considérez quelques exemples de requêtes, il n'est pas toujours clair quand utiliser ces deux méthodes:

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?

Question

Quelles conventions suivez-vous ou suggérez-vous lorsque vous décidez d'utiliser SingleOrDefault()et FirstOrDefault()dans vos requêtes LINQ?

p.campbell
la source

Réponses:

466

Chaque fois que vous utilisez SingleOrDefault, vous indiquez clairement que la requête doit aboutir au plus à un seul résultat. D'un autre côté, lorsque FirstOrDefaultest utilisé, la requête peut renvoyer n'importe quelle quantité de résultats mais vous déclarez que vous ne voulez que le premier.

Personnellement, je trouve la sémantique très différente et l'utilisation de la bonne, en fonction des résultats attendus, améliore la lisibilité.

Bryan Menard
la source
164
Une différence très importante est que si vous utilisez SingleOrDefault sur une séquence avec plusieurs éléments, il lève une exception.
Kamran Bigdely du
17
@kami s'il ne lançait pas d'exception, ce serait exactement comme FirstOrDefault. L'exception est ce qui en fait SingleOrDefault. Bon point pour le remonter et mettre un clou sur le cercueil des différences.
Fabio S.
17
Je dois dire que du point de vue des performances, le FirstOrDefault fonctionne environ 10 fois plus rapidement que SingleOrDefault, en utilisant une liste <MyClass> de 9 000 000 éléments, la classe contient 2 entiers et le Func contient une recherche de ces deux entiers. La recherche 200 fois dans une boucle a pris 22 secondes sur var v = list.SingleOrDefault (x => x.Id1 == i && x.Id2 == i); et var v = list.FirstOrDefault (x => x.Id1 == i && x.Id2 == i); environ 3 secondes
Chen
6
@BitsandBytesHandyman Si SignleOrDefault ne lançait pas d'exception lorsque la séquence contenait plusieurs éléments, elle ne se comporterait pas exactement comme FirstOrDefault. FirstOrDefault renvoie le premier élément, ou null si la séquence est vide. SingleOrDefault doit renvoyer le seul élément, ou null si la séquence est vide OU si elle contient plusieurs éléments, sans lever d'exception du tout.
Thanasis Ioannidis
2
@RSW Oui, j'en suis conscient. En lisant attentivement mon commentaire, je disais ce que SingleOrDefault devrait faire, pas ce qu'il fait. Mais bien sûr, ce qu'il devrait faire est très subjectif. Pour moi, le modèle "SomethingOrDefault" signifie: Obtenez la valeur de "Something". Si "quelque chose" ne parvient pas à renvoyer une valeur, renvoyez la valeur par défaut. Ce qui signifie que la valeur par défaut doit être retournée même dans le cas où "Quelque chose" lèverait une exception. Ainsi, où Single lèverait une exception, SingleOrDefault devrait retourner la valeur par défaut, à mon avis.
Thanasis Ioannidis
585

Si votre jeu de résultats renvoie 0 enregistrements:

  • SingleOrDefault renvoie la valeur par défaut du type (par exemple, la valeur par défaut pour int est 0)
  • FirstOrDefault renvoie la valeur par défaut du type

Si votre jeu de résultats renvoie 1 enregistrement:

  • SingleOrDefault renvoie cet enregistrement
  • FirstOrDefault renvoie cet enregistrement

Si votre jeu de résultats renvoie de nombreux enregistrements:

  • SingleOrDefault lève une exception
  • FirstOrDefault renvoie le premier enregistrement

Conclusion:

Si vous souhaitez qu'une exception soit levée si le jeu de résultats contient de nombreux enregistrements, utilisez SingleOrDefault.

Si vous voulez toujours 1 enregistrement, quel que soit le jeu de résultats, utilisez FirstOrDefault

Alex
la source
6
Je dirais qu'il est rare de vouloir réellement l'exception donc la plupart du temps FirstOrDefault serait préféré. Je sais que des cas existeraient tout simplement pas si souvent imo.
MikeKulls
FirstOrDefaultest retourné premier enregistrement signifie nouvel enregistrement (dernier) / ancien enregistrement (premier)? pouvez-vous me préciser?
Duk
@Duk, cela dépend de la façon dont vous triez les enregistrements. Vous pouvez utiliser OrderBy () ou OrderByDescending () etc. avant d'appeler FirstOrDefault. Voir l'exemple de code de l'OP.
Gan
5
J'aime aussi cette réponse. Surtout étant donné qu'il y a des occasions où vous voulez réellement que l'exception soit levée parce que vous avez l'intention de gérer ce cas rare correctement ailleurs, au lieu de simplement prétendre que cela ne se produit pas. Lorsque vous voulez l'exception, vous le dites clairement, et forcez également les autres à gérer simplement le boîtier, ce qui rend le système global plus robuste.
Francis Rodgers
Il est très clairement énoncé pour que l'on puisse facilement comprendre.
Nirav Vasoya
244

Il y a

  • une différence sémantique
  • une différence de performance

entre les deux.

Différence sémantique:

  • FirstOrDefault renvoie un premier élément potentiellement multiple (ou par défaut s'il n'en existe aucun).
  • SingleOrDefaultsuppose qu'il existe un seul élément et le renvoie (ou par défaut s'il n'en existe aucun). Plusieurs articles constituent une violation du contrat, une exception est levée.

Différence de performance

  • FirstOrDefaultest généralement plus rapide, il itère jusqu'à ce qu'il trouve l'élément et n'a à répéter l'ensemble de l'énumérable que lorsqu'il ne le trouve pas. Dans de nombreux cas, il y a une forte probabilité de trouver un article.

  • SingleOrDefaultdoit vérifier s'il n'y a qu'un seul élément et itère donc toujours l'ensemble de l'énumérable. Pour être précis, il itère jusqu'à ce qu'il trouve un deuxième élément et lève une exception. Mais dans la plupart des cas, il n'y a pas de second élément.

Conclusion

  • À utiliser FirstOrDefaultsi vous ne vous souciez pas du nombre d'articles ou si vous ne pouvez pas vous permettre de vérifier l'unicité (par exemple dans une très grande collection). Lorsque vous vérifiez l'unicité de l'ajout des éléments à la collection, il peut être trop coûteux de le vérifier à nouveau lors de la recherche de ces éléments.

  • À utiliser SingleOrDefaultsi vous n'avez pas trop à vous soucier des performances et que vous voulez vous assurer que l'hypothèse d'un seul élément est claire pour le lecteur et vérifiée lors de l'exécution.

En pratique, vous utilisez First/ FirstOrDefaultsouvent même dans les cas où vous supposez un seul élément, pour améliorer les performances. Vous devez toujours vous rappeler que Single/ SingleOrDefaultpeut améliorer la lisibilité (car il énonce l'hypothèse d'un seul élément) et la stabilité (car il le vérifie) et l'utiliser de manière appropriée.

Stefan Steinegger
la source
16
+1 "ou lorsque vous ne pouvez pas vous permettre de vérifier l'unicité (par exemple dans une très grande collection)." . Je cherchais ça. J'ajouterais également appliquer l'unicité lors de l'insertion, ou / et par conception au lieu de lors de la requête!
Nawaz
Je peux imaginer SingleOrDefaultitérer sur un grand nombre d'objets lors de l'utilisation de Linq to Objects, mais n'a pas SingleOrDefaultà répéter au plus 2 éléments si Linq parle à une base de données par exemple? Je me demande juste ..
Memet Olsen
3
@memetolsen Considérez la broche de code pour les deux avec LINQ to SQL - FirstOrDefault utilise Top 1. SingleOrDefault utilise Top 2.
Jim Wooley
@JimWooley Je suppose que j'ai mal compris le mot «énumérable». Je pensais que Stefan voulait dire le C # Enumerable.
Memet Olsen
1
@memetolsen correct en termes de réponse d'origine, votre commentaire faisait référence à la base de données, donc je proposais ce qui se passait du fournisseur. Alors que le code .Net itère uniquement sur 2 valeurs, la base de données visite autant d'enregistrements que nécessaire jusqu'à ce qu'il atteigne le second répondant aux critères.
Jim Wooley
76

Personne n'a mentionné que FirstOrDefault traduit en SQL fait TOP 1 enregistrement, et SingleOrDefault fait TOP 2, car il a besoin de savoir qu'il y a plus d'un enregistrement.

shalke
la source
3
Lorsque j'ai exécuté un SingleOrDefault via LinqPad et VS, je n'ai jamais obtenu SELECT TOP 2, avec FirstOrDefault j'ai pu obtenir SELECT TOP 1, mais pour autant que je sache, n'obtenez pas SELECT TOP 2.
Jamie R Rytlewski
Hé, j'ai également été essayé dans linqpad et la requête SQL m'a fait peur car il récupère complètement toutes les lignes. Je ne sais pas comment cela peut se produire?
AnyOne
1
Cela dépend entièrement du fournisseur LINQ utilisé. Par exemple, LINQ to SQL et LINQ to Entities peuvent se traduire en SQL de différentes manières. Je viens d'essayer LINQPad avec le fournisseur IQ MySql, et FirstOrDefault()ajoute LIMIT 0,1tout en SingleOrDefault()n'ajoute rien.
Lucas
1
EF Core 2.1 traduit FirstOrDefault en SELECT TOP (1), SingleOrDefault en SELECT TOP (2)
camainc
19

Pour LINQ -> SQL:

SingleOrDefault

  • générera une requête telle que "sélectionner * des utilisateurs où userid = 1"
  • Sélectionnez l'enregistrement correspondant, lève une exception si plusieurs enregistrements sont trouvés
  • À utiliser si vous récupérez des données en fonction de la colonne de clé primaire / unique

FirstOrDefault

  • générera une requête telle que "sélectionner le top 1 * des utilisateurs où userid = 1"
  • Sélectionnez les premières lignes correspondantes
  • À utiliser si vous récupérez des données sur la base d'une colonne de clé non principale / unique
Prakash
la source
Je pense que vous devriez supprimer "Sélectionner toutes les lignes correspondantes" de SingleOrDefault
Saim Abdullah
10

J'utilise SingleOrDefaultdans des situations où ma logique dicte que le résultat sera soit zéro soit un résultat. S'il y en a plus, c'est une situation d'erreur, ce qui est utile.

dépensier
la source
3
Souvent, je trouve que SingleOrDefault () met en évidence les cas où je n'ai pas appliqué le filtrage correct sur l'ensemble de résultats, ou où il y a un problème de duplication dans les données sous-jacentes. Le plus souvent, je me retrouve à utiliser Single () et SingleOrDefault () sur les méthodes First ().
TimS
Il y a des implications en termes de performances pour Single () et SingleOrDefault () sur LINQ to Objects si vous avez un grand énumérable, mais lorsque vous parlez à une base de données (par exemple SQL Server), il fera un appel au top 2 et si vos index sont correctement configurés, l'appel ne devrait pas être coûteux et je préfère échouer rapidement et trouver le problème de données au lieu d'introduire éventuellement d'autres problèmes de données en prenant le mauvais double lors de l'appel à First () ou FirstOrDefault ().
heartlandcoder
5

SingleOrDefault: vous dites que "au plus" il y a un élément correspondant à la requête ou par défaut FirstOrDefault: vous dites qu'il y a "au moins" un élément correspondant à la requête ou par défaut

Dites-le à voix haute la prochaine fois que vous devez choisir et vous choisirez probablement judicieusement. :)

Steven
la source
5
En fait, l'absence de résultats est une utilisation parfaitement acceptable de FirstOrDefault. More correctly: FirstOrDefault` = N'importe quel nombre de résultats, mais je ne me soucie que du premier, il peut également n'y avoir aucun résultat. SingleOrDefault= Il y a 1 ou 0 résultats, s'il y en a plus, cela signifie qu'il y a une erreur quelque part. First= Il y a au moins un résultat, et je le veux. Single= Il y a exactement 1 résultat, ni plus, ni moins, et je veux celui-là.
Davy8
4

Dans vos cas, j'utiliserais ce qui suit:

sélectionnez par ID == 5: c'est OK d'utiliser SingleOrDefault ici, car vous vous attendez à une [ou aucune] entité, si vous avez plus d'une entité avec ID 5, il y a quelque chose de mal et certainement une exception digne.

lors de la recherche de personnes dont le prénom est égal à "Bobby", il peut y en avoir plusieurs (très probablement, je pense), vous ne devez donc ni utiliser Single ni First, sélectionnez simplement avec l'opération Where (si "Bobby" renvoie trop entités, l'utilisateur doit affiner sa recherche ou choisir l'un des résultats renvoyés)

l'ordre par date de création doit également être effectué avec une opération Where (il est peu probable qu'il n'y ait qu'une seule entité, le tri ne serait pas très utile;) cela implique cependant que vous voulez que TOUTES les entités soient triées - si vous n'en voulez qu'une, utilisez FirstOrDefault, Single lancerait à chaque fois si vous aviez plus d'une entité.

Olli
la source
3
Je ne suis pas d'accord. Si votre ID de base de données est une clé primaire, la base de données applique déjà l'unicité. Gaspiller le cycle CPU pour vérifier si la base de données fait son travail sur chaque requête est tout simplement stupide.
John Henckel, le
4

Les deux sont les opérateurs d'élément et ils sont utilisés pour sélectionner un seul élément dans une séquence. Mais il y a une différence mineure entre eux. L'opérateur SingleOrDefault () lèverait une exception si plusieurs éléments sont satisfaits, la condition où FirstOrDefault () ne lèvera aucune exception pour la même chose. Voici l'exemple.

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
la source
2
"il y a une différence mineure entre eux" - C'est majeur!
Nikhil Vartak
3

Dans votre dernier exemple:

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

Oui. Si vous essayez d'utiliser SingleOrDefault()et que la requête aboutit à plus d'un enregistrement, vous obtiendrez une exception. Le seul moment où vous pouvez utiliser en toute sécurité SingleOrDefault()est lorsque vous n'attendez qu'un seul et un seul résultat ...

bytebender
la source
C'est correct. Si vous obtenez 0 résultat, vous obtenez également une exception.
Dennis Rongo
1

Donc, si je comprends bien maintenant, SingleOrDefault sera bien si vous recherchez des données qui sont garanties d'être uniques, c'est-à-dire appliquées par des contraintes de base de données comme la clé primaire.

Ou existe-t-il un meilleur moyen d'interroger la clé primaire?

En supposant que ma TableAcc a

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

et je veux demander un AccountNumber 987654, j'utilise

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

À mon avis, FirstOrDefaultest beaucoup surutilisé. Dans la majorité des cas, lorsque vous filtrez des données, vous vous attendez à récupérer une collection d'éléments correspondant à la condition logique ou un seul élément unique par son identifiant unique - tel qu'un utilisateur, un livre, une publication, etc. pourquoi nous pouvons même aller jusqu'à dire que FirstOrDefault()c'est une odeur de code non pas parce qu'il y a quelque chose qui ne va pas mais parce qu'il est utilisé trop souvent. Ce billet de blog explore le sujet en détail. La plupart du temps, l'OMI SingleOrDefault()est une bien meilleure alternative, alors faites attention à cette erreur et assurez-vous d'utiliser la méthode la plus appropriée qui représente clairement votre contrat et vos attentes.

Vasil Kosturski
la source
-1

Une chose qui manque dans les réponses ...

S'il y a plusieurs résultats, FirstOrDefault sans commande par peut ramener des résultats différents en fonction de la stratégie d'indexation utilisée par le serveur.

Personnellement, je ne supporte pas de voir FirstOrDefault dans le code car pour moi, cela dit que le développeur ne se souciait pas des résultats. Avec une commande par, elle peut être utile comme moyen de faire respecter le plus récent / le plus tôt. J'ai dû corriger de nombreux problèmes causés par des développeurs imprudents utilisant FirstOrDefault.

El Dude
la source
-2

J'ai interrogé Google pour l'utilisation des différentes méthodes sur GitHub. Pour ce faire, exécutez une requête de recherche Google pour chaque méthode et limitez la requête au domaine github.com et à l'extension de fichier .cs en utilisant la requête "site: fichier github.com: cs ..."

Il semble que les méthodes First * soient plus couramment utilisées que les méthodes Single *.

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

Je ne comprends pas pourquoi vous utilisez FirstOrDefault(x=> x.ID == key)alors que cela pourrait récupérer des résultats beaucoup plus rapidement si vous utilisez Find(key). Si vous interrogez avec la clé primaire de la table, la règle générale est de toujours utiliser Find(key). FirstOrDefaultdevrait être utilisé pour des trucs de prédicat comme (x=> x.Username == username)etc.

cela ne méritait pas un downvote car le titre de la question n'était pas spécifique à linq sur DB ou Linq à List / IEnumerable etc.

Theron Govender
la source
1
Dans quel espace de noms se trouve Find()?
p.campbell
Pourriez-vous nous dire s'il vous plaît? Toujours en attente d'une réponse.
Denny
Cela pourrait être: stackoverflow.com/questions/14032709/…
Jeppe
Le mot "IEnumerable" est dans la toute première ligne du corps de la question. Si vous ne lisez que le titre et non la question réelle et que vous avez posté une réponse incorrecte, c'est votre erreur et une raison parfaitement légitime de voter contre l'OMI.
F1Krazy