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

Considérons les méthodes d’extension IEnumerable SingleOrDefault() et FirstOrDefault()

Documents MSDN que SingleOrDefault :

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.

alors que FirstOrDefault de MSDN (probablement lorsqu’on utilise un OrderBy() ou un OrderByDescending() ou aucun),

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

Considérons une poignée d’exemples de requêtes, il n’est pas toujours évident d’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 SingleOrDefault() utiliser SingleOrDefault() et FirstOrDefault() dans vos requêtes LINQ?

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

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

Si votre jeu de résultats renvoie 0 enregistrement:

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

Si vous obtenez un résultat, le résultat retournera 1 enregistrement:

  • SingleOrDefault renvoie cet enregistrement
  • FirstOrDefault renvoie cet enregistrement

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

  • SingleOrDefault lance 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 un enregistrement quel que soit le résultat, utilisez FirstOrDefault

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 pas).
  • SingleOrDefault suppose qu’il existe un seul élément et le renvoie (ou par défaut s’il n’en existe pas). Plusieurs éléments sont une violation de contrat, une exception est levée.

Différence de performance

  • FirstOrDefault est généralement plus rapide, il itère jusqu’à ce qu’il trouve l’élément et ne doit parcourir que l’énumérable entière quand il ne la trouve pas. Dans de nombreux cas, il existe une forte probabilité de trouver un article.

  • SingleOrDefault doit vérifier s’il y a un seul élément et donc itère toujours l’intégralité de l’énumérateur. Pour être précis, il itère jusqu’à ce qu’il trouve un deuxième élément et lance une exception. Mais dans la plupart des cas, il n’y a pas de second élément.

Conclusion

  • Utilisez FirstOrDefault si vous ne vous souciez pas du nombre d’éléments 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é lors de l’ajout d’éléments à la collection, il peut s’avérer trop coûteux de la vérifier à nouveau lors de la recherche de ces éléments.

  • Utilisez SingleOrDefault si vous ne devez pas trop vous soucier des performances et que vous voulez vous assurer que le contenu d’un élément est clair pour le lecteur et vérifié au moment de l’exécution.

En pratique, vous utilisez souvent First / FirstOrDefault même dans les cas où vous supposez un seul élément, pour améliorer les performances. Rappelez-vous que Single / SingleOrDefault peut améliorer la lisibilité (car il suppose l’hypothèse d’un seul élément) et la stabilité (car il vérifie) et l’utiliser correctement.

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

Pour LINQ -> SQL:

SingleOrDefault

  • générera une requête comme “select * from users où userid = 1”
  • Sélectionnez l’enregistrement correspondant, Lance une exception si plusieurs enregistrements ont été trouvés
  • Utilisez si vous récupérez des données en fonction de la colonne de clé primaire / unique

FirstOrDefault

  • générera une requête comme “select top 1 * des utilisateurs où userid = 1”
  • Sélectionnez les premières lignes correspondantes
  • Utilisez si vous récupérez des données en fonction d’une colonne de clé non primaire / unique

J’utilise SingleOrDefault dans des situations où ma logique exige que le résultat soit zéro ou un. S’il y en a plus, c’est une situation d’erreur, ce qui est utile.

SingleOrDefault: Vous dites que “Au plus” il y a un élément correspondant à la requête ou à la valeur par défaut FirstOrDefault: vous dites qu’il y a “Au moins” un élément correspondant à la requête ou à la valeur par défaut

Dites-le à haute voix la prochaine fois que vous devez choisir et vous choisirez probablement avec sagesse. 🙂

Dans vos cas, j’utiliserais ce qui suit:

select by ID == 5: il est correct d’utiliser SingleOrDefault ici, car vous attendez une entité [ou aucune], si vous avez plus d’une entité avec ID 5, il y a quelque chose qui ne va pas et certainement une exception.

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 devriez donc pas utiliser Single ni First, sélectionner simplement avec Where-operation (si “Bobby” retourne trop entités, l’utilisateur doit affiner sa recherche ou choisir l’un des résultats retourné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 sorting ne sera pas d’une grande utilité;) cela implique cependant que TOUTES les entités soient sortingées. Single serait lancé à chaque fois si vous avez plusieurs entités.

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 génère plus qu’un enregistrement, vous obtiendrez une exception. Le seul moment où vous pouvez utiliser SingleOrDefault() toute sécurité, c’est quand vous n’attendez que 1 et 1 seul résultat …

Les deux sont les opérateurs d’élément et ils sont utilisés pour sélectionner un seul élément d’une séquence. Mais il y a une différence mineure entre eux. L’opérateur SingleOrDefault () émet une exception si plusieurs éléments sont satisfaits si la condition comme FirstOrDefault () ne génère aucune exception pour le même. Voici l’exemple.

 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(); 

Donc, si je comprends bien, SingleOrDefault sera utile si vous interrogez des données garanties uniques, c’est-à-dire imposées par des contraintes de firebase database telles que la clé primaire.

Ou existe-t-il un meilleur moyen d’interroger la clé primaire.

En supposant que mon 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); 

Une chose qui manque dans les réponses …

S’il y a plusieurs résultats, FirstOrDefault sans une commande peut ramener des résultats différents selon la stratégie d’index utilisée par le serveur.

Personnellement, je ne supporte pas de voir FirstOrDefault dans le code car selon moi, le développeur ne se souciait pas des résultats. Avec une commande by it, elle peut être utile pour appliquer les toutes dernières versions. J’ai dû corriger de nombreux problèmes causés par des développeurs imprudents utilisant FirstOrDefault.

Je ne comprends pas pourquoi vous utilisez FirstOrDefault(x=> x.ID == key) lorsque cela peut 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 de base consiste à toujours utiliser Find(key) . FirstOrDefault doit être utilisé pour les éléments de prédicat tels que (x=> x.Username == username) etc.

cela ne méritait pas une décote car l’en-tête de la question n’était pas spécifique à linq on DB ou Linq to List / IEnumerable etc.