LINQ Single vs First

216

LINQ:

Est-il plus efficace d'utiliser l' Single()opérateur First()lorsque je sais avec certitude que la requête renverra un seul enregistrement ?

Y a-t-il une différence?

Ian Vink
la source

Réponses:

312

Si vous attendez un enregistrement Single, il est toujours bon d'être explicite dans votre code.

Je sais que d'autres ont écrit pourquoi vous utilisez l'un ou l'autre, mais j'ai pensé illustrer pourquoi vous ne devriez PAS utiliser l'un, quand vous parlez de l'autre.

Remarque: Dans mon code, je vais généralement utiliser FirstOrDefault()et SingleOrDefault()mais c'est une question différente.

Prenons, par exemple, une table qui stocke Customersdans différentes langues à l'aide d'une clé composite ( ID, Lang):

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

Ce code ci-dessus introduit une erreur logique possible (difficile à tracer). Il renverra plus d'un enregistrement (en supposant que vous avez l'enregistrement client en plusieurs langues) mais il ne renverra toujours que le premier ... qui peut fonctionner parfois ... mais pas d'autres. C'est imprévisible.

Puisque votre intention est de retourner un Customerusage unique Single();

Ce qui suit lèverait une exception (ce que vous voulez dans ce cas):

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

Ensuite, vous vous frappez simplement sur le front et vous dites ... OOPS! J'ai oublié le champ de langue! Voici la bonne version:

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

First() est utile dans le scénario suivant:

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

Il renverra UN objet, et puisque vous utilisez le tri, ce sera l'enregistrement le plus récent qui sera renvoyé.

L'utilisation Single()lorsque vous pensez qu'il doit toujours renvoyer explicitement 1 enregistrement vous aidera à éviter les erreurs de logique.

Armstrongest
la source
76
Les méthodes Single et First peuvent prendre un paramètre d'expression pour le filtrage, donc la fonction Where n'est pas nécessaire. Exemple: Client client = db.Customers.Single (c => c.ID == 5);
Josh Noe
6
@JoshNoe - Je suis curieux, y a-t-il une différence entre customers.Where(predicate).Single() customers.Single(predicate)?
drzaus
9
@drzaus - Logiquement, non, ils filtrent tous les deux les valeurs à renvoyer en fonction du prédicat. Cependant, j'ai vérifié le démontage, et Where (prédicat). Single () a trois instructions supplémentaires dans le cas simple que j'ai fait. Donc, même si je ne suis pas un expert en informatique, mais il semble que les clients. Les célibataires (prédicats) devraient être plus efficaces.
Josh Noe
5
@JoshNoe c'est tout à fait le contraire en fin de compte.
AgentFire
10
@AgentFire Pour sauvegarder votre déclaration
M. Mimpen
72

Single lèvera une exception s'il trouve plus d'un enregistrement correspondant aux critères. First sélectionnera toujours le premier enregistrement de la liste. Si la requête ne renvoie qu'un seul enregistrement, vous pouvez utiliserFirst() .

Les deux lèveront une InvalidOperationExceptionexception si la collection est vide. Vous pouvez également utiliser SingleOrDefault(). Cela ne lèvera pas d'exception si la liste est vide

AlwaysAProgrammer
la source
30

Célibataire()

Renvoie un seul élément spécifique d'une requête

Quand utiliser : si exactement 1 élément est attendu; pas 0 ou plus de 1. Si la liste est vide ou contient plus d'un élément, elle lèvera une exception "La séquence contient plus d'un élément"

SingleOrDefault ()

Renvoie un seul élément spécifique d'une requête ou une valeur par défaut si aucun résultat n'a été trouvé

Quand utiliser : quand 0 ou 1 éléments sont attendus. Il lèvera une exception si la liste contient 2 éléments ou plus.

Première()

Renvoie le premier élément d'une requête avec plusieurs résultats.

Lors de l'utilisation : lorsqu'un ou plusieurs éléments sont attendus et que vous ne souhaitez que le premier. Il lèvera une exception si la liste ne contient aucun élément.

FirstOrDefault ()

Renvoie le premier élément d'une liste avec n'importe quelle quantité d'éléments, ou une valeur par défaut si la liste est vide.

Lors de l'utilisation : lorsque plusieurs éléments sont attendus et que vous ne souhaitez que le premier. Ou la liste est vide et vous voulez une valeur par défaut pour le type spécifié, identique à default(MyObjectType). Par exemple: si le type de liste est, list<int>il retournera le premier numéro de la liste ou 0 si la liste est vide. Si c'est le cas list<string>, il retournera la première chaîne de la liste ou null si la liste est vide.

Diego Mendes
la source
1
Belle explication. Je ne changerais que ce que vous pouvez utiliser Firstlorsque 1 ou plusieurs éléments sont attendus , pas seulement "plus de 1", et FirstOrDefaultavec n'importe quelle quantité d'éléments.
Andrew
18

Il existe une différence sémantique subtile entre ces deux méthodes.

Permet Singlede récupérer le premier (et le seul) élément d'une séquence qui doit contenir un élément et pas plus. Si la séquence contient plus d'un élément, votre invocation deSingle provoquera une exception, car vous avez indiqué qu'il ne devrait y avoir qu'un seul élément.

Permet Firstde récupérer le premier élément d'une séquence pouvant contenir n'importe quel nombre d'éléments. Si la séquence contient plus d'un élément, votre invocation deFirst ne provoquera pas la levée d'une exception puisque vous avez indiqué que vous n'avez besoin que du premier élément de la séquence et que vous vous en fichez s'il en existe d'autres.

Si la séquence ne contient aucun élément, les deux appels de méthode provoquent la levée d'exceptions car les deux méthodes s'attendent à ce qu'au moins un élément soit présent.

Andrew Hare
la source
18

Si vous ne voulez pas spécifiquement qu'une exception soit levée dans le cas où il y a plus d'un élément, utilisezFirst() .

Les deux sont efficaces, prenez le premier élément. First()est légèrement plus efficace car cela ne prend pas la peine de vérifier s'il y a un deuxième élément.

La seule différence est qu'il Single()s'attend à ce qu'il n'y ait qu'un seul élément dans l'énumération pour commencer, et lève une exception s'il y en a plusieurs. Vous utilisez .Single() si vous souhaitez spécifiquement une exception levée dans ce cas.

Patrick Karcher
la source
14

Si je me souviens, Single () vérifie s'il y a un autre élément après le premier (et lève une exception si c'est le cas), tandis que First () s'arrête après l'avoir obtenu. Les deux lèvent une exception si la séquence est vide.

Personnellement, j'utilise toujours First ().

Etienne de Martel
la source
2
Dans le SQL, ils produisent First () fait TOP 1 et Single () fait TOP 2 si je ne me trompe pas.
Matthijs Wessels
10

En ce qui concerne la performance: un collègue et moi discutions des performances de Single vs First (ou SingleOrDefault vs FirstOrDefault), et je plaidais pour que First (ou FirstOrDefault) soit plus rapide et améliore les performances (je suis tout à fait de faire notre application cours plus vite).

J'ai lu plusieurs articles sur Stack Overflow qui en débattent. Certains disent qu'il y a de petits gains de performances en utilisant First au lieu de Single. En effet, First renvoie simplement le premier élément tandis que Single doit analyser tous les résultats pour s'assurer qu'il n'y a pas de doublon (c'est-à-dire: s'il a trouvé l'élément dans la première ligne du tableau, il analysera toujours toutes les autres lignes pour assurez-vous qu'il n'y a pas de deuxième valeur correspondant à la condition, ce qui entraînerait une erreur). Je me sentais comme si j'étais sur un terrain solide, "First" étant plus rapide que "Single", j'ai donc décidé de le prouver et de mettre fin au débat.

J'ai configuré un test dans ma base de données et ajouté 1 000 000 de lignes d'ID UniqueIdentifier Foreign UniqueIdentifier Info nvarchar (50) (rempli de chaînes de chiffres de «0» à «999,9999»

J'ai chargé les données et défini l'ID comme champ de clé primaire.

En utilisant LinqPad, mon objectif était de montrer que si vous recherchiez une valeur sur 'Foreign' ou 'Info' en utilisant Single, ce serait bien pire que d'utiliser First.

Je ne peux pas expliquer les résultats que j'ai obtenus. Dans presque tous les cas, l'utilisation de Single ou SingleOrDefault était légèrement plus rapide. Cela n'a aucun sens logique pour moi, mais je voulais partager cela.

Ex: j'ai utilisé les requêtes suivantes:

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

J'ai essayé des requêtes similaires sur le champ de clé 'Étranger' qui n'était pas indexé en pensant que First serait plus rapide, mais Single était toujours légèrement plus rapide dans mes tests.

Marcus Talcott
la source
2
Si elle se trouve sur un champ indexé, la base de données n'a pas besoin d'effectuer une analyse pour s'assurer qu'elle est unique. Il sait déjà que c'est le cas. Il n'y a donc pas de surcharge, et la seule surcharge du côté serveur consiste à s'assurer qu'un seul enregistrement est revenu. Faire des tests de performance moi-même n'est pas concluant d'une manière ou d'une autre
mirhagk
1
Je ne pense pas que ces résultats seraient les mêmes sur un objet complexe si vous cherchiez sur un champ non utilisé par IComparer.
Anthony Nichols
Ce devrait être une autre question. Assurez-vous d'inclure la source de votre test .
Sinatr
5

Ils sont différents. Tous deux affirment que l'ensemble de résultats n'est pas vide, mais single affirme également qu'il n'y a pas plus d'un résultat. Personnellement, j'utilise Single dans les cas où je m'attends à ce qu'il n'y ait qu'un seul résultat, car le fait d'obtenir plus d'un résultat est une erreur et devrait probablement être traité comme tel.

jaltiere
la source
5

Vous pouvez essayer un exemple simple pour faire la différence. Une exception sera levée sur la ligne 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);
Chauskin Rodion
la source
3

Beaucoup de gens que je connais utilisent FirstOrDefault (), mais j'ai tendance à utiliser SingleOrDefault () davantage parce que souvent il y aurait une sorte d'incohérence des données s'il y en avait plusieurs. Il s'agit cependant de LINQ-to-Objects.

Matt H
la source
-1

Les enregistrements dans l'entité Employé:

Employeeid = 1: Un seul employé avec cet ID

Firstname = Robert: Plus d'un employé portant ce nom

Employeeid = 10: Aucun employé avec cet ID

Maintenant, il est nécessaire de comprendre ce Single()que First()signifie et de dire en détail.

Célibataire()

Single () est utilisé pour renvoyer un seul enregistrement qui existe uniquement dans une table, donc la requête ci-dessous renverra l'employé dont employeed =1nous n'avons qu'un seul employé dont la valeur Employeedest 1. Si nous avons deux enregistrements pour EmployeeId = 1cela, il génère une erreur (voir le erreur ci-dessous dans la deuxième requête où nous utilisons un exemple pour Firstname.

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

Ce qui précède renverra un seul enregistrement, qui a 1 employeeId

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

Ce qui précède lèvera une exception car les enregistrements multi-espaces sont dans la table pour FirstName='Robert'. L'exception sera

InvalidOperationException: la séquence contient plusieurs éléments

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

Cela lèvera une fois de plus une exception car aucun enregistrement n'existe pour id = 10. L'exception sera

InvalidOperationException: la séquence ne contient aucun élément.

Car EmployeeId = 10il renverra null, mais comme nous l'utilisons, Single()il générera une erreur. Afin de gérer l'erreur nulle, nous devons utiliserSingleOrDefault() .

Première()

First () renvoie à partir de plusieurs enregistrements les enregistrements correspondants triés par ordre croissant en fonction de birthdatesorte qu'il renvoie «Robert» qui est le plus âgé.

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

Ci-dessus devrait retourner le plus ancien, Robert selon DOB.

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

Ci-dessus lèvera une exception car aucun enregistrement pour id = 10 n'existe. Pour éviter une exception nulle, nous devrions utiliser FirstOrDefault()plutôt queFirst() .

Remarque: Nous pouvons utiliser uniquement First()/ Single()lorsque nous sommes absolument sûrs qu'il ne peut pas retourner une valeur nulle.

Dans les deux fonctions, utilisez SingleOrDefault () OU FirstOrDefault () qui gérera une exception nulle, dans le cas où aucun enregistrement n'a été trouvé, il renverra null.

Shérif
la source
Veuillez expliquer votre réponse.
MrMaavin
1
@MrMaavin J'ai mis à jour, veuillez me faire savoir si c'est compréhensible maintenant pour vous?
Shérif