LINQ одиночний проти першого


215

LINQ:

Чи ефективніше використовувати Single()оператор, First()коли я колись точно знаю, що запит поверне один запис ?

Чи є різниця?

Відповіді:


312

Якщо ви очікуєте єдиного запису, завжди добре бути чітким у своєму коді.

Я знаю, що інші написали, чому ви використовуєте те чи інше, але я подумав, що я проілюструю, чому НЕ слід використовувати одне, коли ви маєте на увазі інше.

Примітка: У моєму коді, я зазвичай використовую FirstOrDefault()і , SingleOrDefault()але це інше питання.

Візьмемо, наприклад, таблицю, яка зберігає Customersрізними мовами за допомогою складеного ключа ( ID, Lang):

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

Цей код вище вводить можливу логічну помилку (важко простежити). Він поверне більше одного запису (якщо у вас є запис клієнта на кількох мовах), але він завжди поверне лише перший ... який може працювати іноді ..., але не інші. Це непередбачувано.

Оскільки ваш намір - повернути Одноразове Customerкористування Single();

Наступне може призвести до виключення (що саме ви хочете в цьому випадку):

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

Тоді, ти просто б'єш себе в лоб і кажеш собі: OOPS! Я забув мовне поле! Далі правильна версія:

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

First() корисний у наступному сценарії:

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

Він поверне ОДИН об’єкт, і оскільки ви використовуєте сортування, це буде останній запис, який повертається.

Використання, Single()коли відчуваєш, що явно завжди повинен повертати 1 запис, допоможе уникнути логічних помилок.


76
І метод Single, і First можуть приймати параметр вираження для фільтрації, тому функція Where не потрібна. Приклад: клієнт-замовник = db.Customers.Single (c => c.ID == 5);
Джош Ное

6
@JoshNoe - мені цікаво, чи є різниця між customers.Where(predicate).Single() customers.Single(predicate)?
drzaus

9
@drzaus - Логічно ні, вони обидва фільтрують значення, що повертаються, виходячи з присудка. Однак я перевірив демонтаж, і де (предикат) .Single () має три додаткові інструкції у простому випадку, який я зробив. Отже, поки я не експерт з ІР, але, схоже, клієнти. Один (предикат) повинен бути більш ефективним.
Джош Ное

5
@JoshNoe це зовсім протилежне, як виявляється.
AgentFire


72

Single викине виняток, якщо знайде більше одного запису, що відповідає критеріям. Перший завжди буде вибирати перший запис зі списку. Якщо запит повертає лише 1 запис, ви можете перейти з ним First().

Обидва викинуть InvalidOperationExceptionвиняток, якщо колекція порожня. Можна також використовувати SingleOrDefault(). Це не призведе до виключення, якщо список порожній


29

Одномісний ()

Повертає окремий конкретний елемент запиту

При використанні : якщо очікується рівно 1 елемент; не 0 або більше 1. Якщо список порожній або містить більше одного елемента, він видасть виняток "Послідовність містить більше одного елемента"

SingleOrDefault ()

Повертає окремий конкретний елемент запиту або значення за замовчуванням, якщо результату не знайдено

При використанні : Коли очікується 0 або 1 елемент. Це буде винятком, якщо в списку є 2 і більше елементів.

Перший()

Повертає перший елемент запиту з кількома результатами.

При використанні : Коли очікується 1 або більше елементів, і ви хочете лише перший. Це буде винятком, якщо список не містить елементів.

FirstOrDefault ()

Повертає перший елемент списку з будь-якою кількістю елементів або значенням за замовчуванням, якщо список порожній.

При використанні : Коли очікується кілька елементів, і ви хочете лише перший. Або список порожній, і ви хочете значення за замовчуванням для вказаного типу, таке ж, як default(MyObjectType). Наприклад: якщо тип списку, list<int>він поверне перше число зі списку або 0, якщо список порожній. Якщо це так list<string>, він поверне перший рядок зі списку або null, якщо список порожній.


1
Приємне пояснення. Я б змінив лише те, що ви можете використовувати, Firstколи очікується 1 або більше елементів , не тільки "більше 1", а FirstOrDefaultтакож будь-яка кількість елементів.
Андрій

18

Між цими двома методами існує тонка, смислова різниця.

Використовуйте Singleдля отримання першого (і єдиного) елемента з послідовності, яка повинна містити один елемент і не більше. Якщо в послідовності більше елемента, ваш виклик Singleвикликає викид виключення, оскільки ви вказали, що має бути лише один елемент.

Використовуйте Firstдля отримання першого елемента з послідовності, яка може містити будь-яку кількість елементів. Якщо в послідовності більше, ніж на елементі, ваш виклик Firstне призведе до викидання винятку, оскільки ви вказали, що вам потрібен лише перший елемент у послідовності, і не хвилюється, чи існує більше.

Якщо послідовність не містить елементів, обидва виклики методу призведуть до викидів винятків, оскільки обидва методи очікують наявності принаймні одного елемента.


17

Якщо ви не хочете, щоб виняток було викинуто у випадку, якщо їх є більше, використовуйтеFirst() .

Обидва ефективні, візьміть перший пункт. First()трохи ефективніше, тому що не турбує перевірку, чи є другий предмет.

Єдина відмінність полягає в тому, що Single()сподіваємось, що в перерахунку буде лише один елемент, і він викине виняток, якщо їх буде більше. Ви використовуєте, .Single() якщо ви конкретно хочете, щоб у цьому випадку було виключено виняток .


14

Якщо я пам'ятаю, Single () перевіряє, чи є інший елемент після першого (і кидає виняток, якщо це так), тоді як First () зупиняється після його отримання. Обидва кидають виняток, якщо послідовність порожня.

Особисто я завжди використовую First ().


2
У SQL вони виробляють First () робить TOP 1, а Single () робить TOP 2, якщо я не помиляюся.
Matthijs Wessels

10

Щодо продуктивності: ми з колегою обговорювали ефективність Single / First (або SingleOrDefault vs FirstOrDefault), і я стверджував, що First (або FirstOrDefault) буде швидше і покращить продуктивність (я все про те, щоб зробити наш додаток бігайте швидше).

Я прочитав кілька публікацій про стек переповнення, які обговорюють це. Деякі кажуть, що невеликі підвищення продуктивності використовують First, а не Single. Це тому, що First просто поверне перший елемент, тоді як Single повинен сканувати всі результати, щоб переконатися, що немає дублікату (тобто: якщо він знайшов елемент у першому рядку таблиці, він все одно скануватиме кожен інший рядок у переконайтеся, що немає другого значення, яке відповідає умові, яке потім призведе до помилки). Я відчував, що перебуваю на твердій землі, коли "Перший" виявляється швидшим за "Одномісний", тому я надумав це довести і перестав дискусію в спокій.

Я встановив тест у своїй базі даних та додав 1 000 000 рядків ID UniqueIdentifier Foreign UniqueIdentifier Info nvarchar (50) (заповнений рядками цифр "0" до "999,9999"

Я завантажив дані та встановив ідентифікатор як поле первинного ключа.

Використовуючи LinqPad, моєю метою було показати, що якщо ви шукаєте значення на "Foreign" або "Info" за допомогою Single, це буде набагато гірше, ніж використання First.

Я не можу пояснити отримані результати. Майже у кожному випадку використання Single або SingleOrDefault було трохи швидше. Це не має для мене ніякого логічного сенсу, але я хотів поділитися цим.

Наприклад: я використав такі запити:

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

Я спробував подібні запити в ключовому полі "Іноземне", яке не було індексованим мисленням, яке виявило б "Перше" швидше, але "Один" завжди був трохи швидшим у своїх тестах.


2
Якщо він знаходиться в індексованому полі, то в базі даних не потрібно робити сканування, щоб переконатися, що він унікальний. Це вже знає, що це так. Таким чином, немає накладних витрат там, і лише накладні витрати на стороні сервера забезпечують повернення лише одного запису. Робити тести на працездатність я так чи інакше не переконливо
mirhagk

1
Я не думаю, що ці результати були б однаковими для складного об'єкта, якби ви шукали на полі, яке не використовується IComparer.
Ентоні Ніколс

Це має бути ще одним питанням. Обов’язково додайте джерело свого тесту .
Сінатр

5

Вони різні. Обидва вони стверджують, що набір результатів не порожній, але одиничний також стверджує, що не більше 1 результату. Я особисто використовую Single в тих випадках, коли я очікую лише 1 результат, тому що повернення більш ніж одного результату є помилкою, і, ймовірно, слід ставитися до цього.


5

Ви можете спробувати простий приклад, щоб отримати різницю. Виняток буде кинутий у рядку 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);

3

Дуже багато людей, яких я знаю, використовують FirstOrDefault (), але я схильний використовувати SingleOrDefault () більше, тому що часто це буде якась невідповідність даних, якби їх було більше. Однак це стосується LINQ до об'єктів.


-1

Записи в особі працівника:

Employeeid = 1: Лише один працівник із цим посвідченням особи

Firstname = Robert: Більше одного працівника з таким ім’ям

Employeeid = 10: Жоден працівник із цим посвідченням особи

Тепер потрібно зрозуміти, що Single()і що First()означає докладно.

Одномісний ()

Single () використовується для повернення єдиного запису, який однозначно існує в таблиці, тому запит нижче поверне працівника, чий у employeed =1нас є лише один Співробітник, чий Employeed1. Якщо у нас є два записи, EmployeeId = 1він видає помилку (див. помилка нижче в другому запиті, для якого ми використовуємо приклад Firstname.

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

Наведене поверне єдиний запис, у якому 1 employeeId

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

Вище викладене виняток, оскільки записи багатоядерності є в таблиці для FirstName='Robert'. Виняток буде

InvalidOperationException: Послідовність містить більше одного елемента

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

Це знову ж таки призведе до виключення, оскільки для id = 10 не існує запису. Виняток буде

InvalidOperationException: Послідовність не містить елементів.

Для EmployeeId = 10поверне нуль, але , як ми використовуємо Single()це буде згенеровано повідомлення про помилку. Для обробки нульової помилки нам слід скористатися SingleOrDefault().

Перший()

Перший () повертає з декількох записів відповідні записи, відсортовані у порядку зростання відповідно до birthdateтого, він поверне «Роберт», який є найстарішим.

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

Зверху слід повернути найстаріший, Роберт, згідно з DOB.

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

Вище буде викинуто виняток, оскільки не існує запису для id = 10. Щоб уникнути нульового винятку, ми повинні використовувати, FirstOrDefault()а не First().

Примітка. Ми можемо використовувати лише First()/ Single()коли ми абсолютно впевнені, що він не може повернути нульове значення.

В обох функціях використовуйте SingleOrDefault () OR FirstOrDefault (), який буде обробляти нульовий виняток, у випадку відсутності запису він поверне null.


Будь ласка, поясніть свою відповідь.
MrMaavin

1
@MrMaavin Я оновив, будь ласка, повідомте мені, чи зрозуміло це для вас зараз?
Шериф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.