LINQ: Коли використовувати SingleOrDefault проти FirstOrDefault () з критеріями фільтрації


505

Розглянемо IE численні методи розширення SingleOrDefault()таFirstOrDefault()

MSDN документи, якіSingleOrDefault :

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

в той час як FirstOrDefaultз MSDN (імовірно при використанні OrderBy()або OrderByDescending()або взагалі нічого),

Повертає перший елемент послідовності

Розглянемо кілька прикладів запитів, не завжди зрозуміло, коли використовувати ці два методи:

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?

Питання

Яких умов ви дотримуєтесь чи пропонуєте, вирішуючи використовувати SingleOrDefault()та FirstOrDefault()у своїх LINQ-запитах?

Відповіді:


465

Щоразу, коли ви користуєтесь SingleOrDefault, ви чітко заявляєте, що запит повинен мати максимум один результат. З іншого боку, коли FirstOrDefaultвикористовується, запит може повернути будь-яку кількість результатів, але ви заявляєте, що хочете лише перший.

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


164
Дуже важлива відмінність полягає в тому, що якщо ви використовуєте SingleOrDefault в послідовності з більш ніж одним елементом, він видає виняток.
Камран Бігделі

17
@kami, якби це не кидало винятку, це було б точно як FirstOrDefault. Виняток становить те, що робить його SingleOrDefault. Хороший момент піднести його і покласти цвях на труну розбіжностей.
Фабіо С.

17
Треба сказати, що з точки зору продуктивності, FirstOrDefault працює приблизно в 10 разів швидше, ніж SingleOrDefault, використовуючи Список <MyClass> з 9000000 елементів, клас містить 2 цілих числа, а Func містить пошук цих двох цілих чисел. Пошук 200 разів у циклі займав 22 секунди на var v = list.SingleOrDefault (x => x.Id1 == i && x.Id2 == i); і var v = list.FirstOrDefault (x => x.Id1 == i && x.Id2 == i); близько 3 секунд
Чень

6
@BitsandBytesHandyman Якби SignleOrDefault не кидав винятку, коли послідовність містила більше одного елемента, вона не поводитиметься так само, як FirstOrDefault. FirstOrDefault повертає перший елемент або null, якщо послідовність порожня. SingleOrDefault повинен повернути єдиний елемент або null, якщо послідовність порожня АБО, якщо вона містить більше ніж один елемент, не викидаючи виняток взагалі.
Танасіс Іоаннідіс

2
@RSW Так, я знаю про це. Уважно читаючи мій коментар, я говорив, що повинен робити SingleOrDefault, а не те, що він робить. Але звичайно, те, що вона повинна робити, дуже суб’єктивно. Для мене шаблон "SomethingOrDefault" означає: Отримайте значення "Something". Якщо "Щось" не поверне значення, поверніть значення за замовчуванням. Що означає, що значення за замовчуванням слід повернути навіть у тому випадку, коли "Щось" кине виняток. Отже, де Single викине виняток, SingleOrDefault повинен повернути значення за замовчуванням, на мій погляд.
Танасіс Іоаннідіс

585

Якщо ваш набір результатів повертає 0 записів:

  • SingleOrDefault повертає значення за замовчуванням для типу (наприклад, за замовчуванням для int дорівнює 0)
  • FirstOrDefault повертає значення за замовчуванням для типу

Якщо результат набору повертає 1 запис:

  • SingleOrDefault повертає цей запис
  • FirstOrDefault повертає цей запис

Якщо ваш набір результатів повертає багато записів:

  • SingleOrDefault кидає виняток
  • FirstOrDefault повертає перший запис

Висновок:

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

Якщо ви завжди хочете 1 запис незалежно від того, що містить набір результатів, використовуйте FirstOrDefault


6
Я б припустив, що дуже рідко хочеться виключення, тому більшу частину часу віддається перевазі FirstOrDefault. Я знаю, що випадки існували б не дуже часто.
MikeKulls

FirstOrDefaultповертається перший запис означає новий запис (останній) / старий запис (перший)? Ви можете мені уточнити?
Дук

@Duk, це залежить від сортування записів. Ви можете використовувати OrderBy () або OrderByDescending () тощо перед тим, як викликати FirstOrDefault. Дивіться приклад коду ОП.
Ган

5
Мені подобається і ця відповідь. Особливо, враховуючи, що є випадки, коли ви хочете, щоб виняток було відкинуто, оскільки ви маєте намір обробляти цей рідкісний випадок належним чином в іншому місці, а не просто робити вигляд, що цього не відбувається. Коли ви хочете, щоб виняток, ви говорите це чітко, а також змушуючи інших працювати, просто роблячи загальну систему більш надійною.
Френсіс Роджерс

Це дуже чітко сказано так, що можна легко зрозуміти.
Нірав Васоя

243

існує

  • семантична різниця
  • різниця в продуктивності

між двома.

Семантична різниця:

  • FirstOrDefault повертає перший елемент потенційно кратного (або за замовчуванням, якщо такого немає).
  • SingleOrDefaultприпускає, що є один елемент, і повертає його (або за замовчуванням, якщо такого немає). Кілька предметів є порушенням договору, викидається виняток.

Різниця у виконанні

  • FirstOrDefaultЗазвичай швидше, воно повторюється до тих пір, поки не знайде елемент і має лише повторити цілий ряд, коли він не знайде його. У багатьох випадках велика ймовірність знайти предмет.

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

Висновок

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

  • Використовуйте, SingleOrDefaultякщо вам не надто турбується про ефективність, і ви хочете переконатися, що припущення про один предмет читачеві зрозуміле та перевірене під час виконання.

На практиці ви використовуєте First/ FirstOrDefaultчасто навіть у випадках, коли ви припускаєте один предмет, для підвищення продуктивності. Ви все ще повинні пам’ятати, що Single/ SingleOrDefaultможе покращити читабельність (тому що вона заявляє про припущення про один предмет) та стабільність (тому що вона перевіряє) та використовувати її належним чином.


16
+1 "або коли ви не можете дозволити перевірку унікальності (наприклад, у дуже великій колекції)." . Я це шукав. Я б також додавав примусового виконання унікальності під час вставки, або / та за дизайном, а не під час запиту!
Наваз

Я можу уявити собі SingleOrDefaultітерацію над багатьма об'єктами при використанні Linq для об'єктів, але не SingleOrDefaultпотрібно повторювати щонайбільше 2 елементи, якщо, наприклад, Linq спілкується з базою даних? Просто цікаво ..
Мемет Олсен

3
@memetolsen Розгляньте кодову косу для двох з LINQ у SQL - FirstOrDefault використовує Top 1. SingleOrDefault використовує Top 2.
Jim Wooley

@JimWooley Я думаю, що я неправильно зрозумів слово "перелічити". Я думав, що Стефан мав на увазі C # Enumerable.
Мемет Олсен

1
@memetolsen правильний з точки зору оригінальної відповіді, ваш коментар посилався на базу даних, тому я пропонував те, що відбувається від провайдера. Хоча код .Net ітератує лише 2 значення, база даних відвідує стільки записів, скільки потрібно, поки не потрапить на другий, що відповідає критеріям.
Джим Вулі

76

Ніхто не згадував, що FirstOrDefault, перекладений на SQL, записує TOP 1, а SingleOrDefault робить TOP 2, тому що йому потрібно знати, чи існує більше 1 запису.


3
Коли я запускав SingleOrDefault через LinqPad та VS, я ніколи не отримував SELECT TOP 2, з FirstOrDefault я зміг отримати SELECT TOP 1, але, наскільки я можу вам сказати, не отримуйте SELECT TOP 2.
Jamie R Rytlewski

Гей, мене теж судили в linqpad, і запит sql змусив мене побоюватися, оскільки він повністю отримує всі рядки. Я не впевнений, як це може статися?
AnyOne

1
Це повністю залежить від використовуваного постачальника LINQ. Наприклад, LINQ до SQL та LINQ до сутностей могли переводити на SQL різними способами. Я просто спробував LINQPad з провайдером IQ MySql, і FirstOrDefault()додає, LIMIT 0,1хоча SingleOrDefault()нічого не додає.
Лукас

1
EF Core 2.1 переводить FirstOrDefault на SELECT TOP (1), SingleOrDefault на SELECT TOP (2)
camainc

19

Для LINQ -> SQL:

SingleOrDefault

  • створить запит на зразок "select * у користувачів, де userid = 1"
  • Виберіть відповідний запис, Викидає виняток, якщо знайдено більше одного запису
  • Використовуйте, якщо ви отримуєте дані на основі стовпця основного / унікального ключа

FirstOrDefault

  • створить запит на зразок "вибрати топ 1 * від користувачів, де userid = 1"
  • Виберіть перші відповідні рядки
  • Використовуйте, якщо ви отримуєте дані на основі стовпця не первинного / унікального ключа

я думаю, ви повинні видалити "Вибрати всі відповідні рядки" з SingleOrDefault
Абдулла

10

Я використовую SingleOrDefaultв ситуаціях, коли моя логіка диктує, що результат буде або нульовим, або одним результатом. Якщо їх більше, це корисна ситуація.


3
Часто я виявляю, що SingleOrDefault () виділяє випадки, коли я не застосував правильну фільтрацію на наборі результатів, або де виникають проблеми з дублюванням базових даних. Найчастіше я знаходжу себе за допомогою методів Single () та SingleOrDefault () за методом First ().
TimS

На LINQ до об’єктів є наслідки для продуктивності для Single () та SingleOrDefault (), якщо у вас є велика кількість, але коли ви спілкуєтесь із базою даних (наприклад, SQL Server), вона здійснить дзвінок у топ-2 та якщо індекси налаштовані правильно, виклик не повинен бути дорогим, і я, швидше, не зможу швидко і знайти проблему даних, а не можливо вводити інші проблеми даних, приймаючи неправильний дублікат при виклику First () або FirstOrDefault ().
heartlandcoder

5

SingleOrDefault: Ви говорите, що "Максимум" є один елемент, що відповідає запиту або за замовчуванням FirstOrDefault: Ви говорите, що є "Принаймні" один елемент, що відповідає запиту або за замовчуванням

Скажіть це вголос наступного разу, коли вам потрібно вибрати, і ви, швидше за все, виберете розумно. :)


5
Насправді відсутні результати - цілком прийнятне використання FirstOrDefault. More correctly: FirstOrDefault` = Будь-яка кількість результатів, але мене цікавить лише перший, результати також можуть бути відсутні. SingleOrDefault= Є 1 або 0 результатів, якщо їх більше, це означає, що десь є помилка. First= Є хоча б один результат, і я цього хочу. Single= Є рівно один результат, не більше, не менше, і я хочу цього.
Davy8

4

У ваших випадках я б використовував наступне:

select by ID == 5: тут нормально використовувати SingleOrDefault, тому що ви очікуєте, що один [або жоден] об'єкт, якщо у вас є більше одного об’єкта з ідентифікатором 5, є щось не так, і, безумовно, варто виняток.

під час пошуку людей, прізвище яких дорівнює «Боббі», може бути більше одного (цілком можливо, я б міг подумати), тому вам не слід ні використовувати Одиночне, ні Перше, просто вибирайте за допомогою «Де-операція» (якщо «Бобі» повертається занадто багато суб'єкти, користувач повинен уточнити свій пошук або вибрати один із повернених результатів)

замовлення за датою створення також слід виконувати з операцією Where (навряд чи буде мати лише одне ціле, сортування не принесе великої користі;), однак, це означає, що ви хочете відсортувати ВСІ об'єкти - якщо ви хочете лише ONE, використовуйте FirstOrDefault, Сингл кидає кожного разу, якщо у вас є більше однієї сутності.


3
Я не погоджуюсь. Якщо ваш ідентифікатор бази даних є первинним ключем, то база даних вже застосовує унікальність. Витрата циклу процесора, щоб перевірити, чи база даних виконує свою роботу за кожним запитом, просто нерозумно.
Джон Хенкель

4

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

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

2
"між ними є незначна різниця" - Це головне!
Нікіл Вартак

3

У вашому останньому прикладі:

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

Так. Якщо ви спробуєте використати SingleOrDefault()і результат запиту в більшій кількості записів, ви отримаєте і виняток. Єдиний час, коли ви можете сміливо користуватися SingleOrDefault()- це коли ви очікуєте лише 1 і лише 1 результат ...


Це правильно. Якщо ви отримаєте 0 результат, ви також отримуєте виняток.
Денніс Ронго

1

Тож, як я зараз розумію, SingleOrDefaultбуде добре, якщо ви будете запитувати дані, які гарантовано є унікальними, тобто виконуються обмеженнями БД, як первинний ключ.

Або є кращий спосіб запиту первинного ключа.

Якщо припустити, що мій TableAcc є

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

і я хочу запитувати AccountNumber 987654, я використовую

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

1

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


-1

Одне, що пропускається у відповідях….

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

Особисто я не можу витримати FirstOrDefault в коді, оскільки мені це каже, що розробник не піклувався про результати. З наказом, хоча це може бути корисним як спосіб примусового виконання останнього / раннього. Мені довелося виправити багато проблем, викликаних недбалими розробниками, які використовують FirstOrDefault.


-2

Я запитав Google про використання різних методів на GitHub. Це робиться шляхом запуску пошукового запиту Google для кожного методу та обмеження запиту до домену github.com та розширення файлу .cs за допомогою запиту "site: github.com file: cs ..."

Схоже, методи First * частіше використовуються, ніж методи Single *.

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

-8

Я не розумію, чому ви використовуєте, FirstOrDefault(x=> x.ID == key)коли це може отримати результати набагато швидше, якщо ви користуєтесь Find(key). Якщо ви запитуєте за допомогою Первинного ключа таблиці, завжди слід використовувати правило Find(key). FirstOrDefaultслід використовувати для таких предикатних речей(x=> x.Username == username) тощо.

це не заслуговувало зворотного голосу, оскільки заголовок питання не був специфічним для linq у БД або Linq до списку / IEnumerable тощо.


1
У якому просторі імен є Find()?
p.campbell

Не могли б ви сказати нам, будь ласка? Ще чекаю на відповідь.
Денні


Слово "IEnumerable" знаходиться в першому рядку тіла запитання. Якщо ви читаєте лише заголовок, а не власне запитання, і в результаті розміщували невірну відповідь, це ваша помилка та цілком законна причина відмовитись від ІМО.
F1Крази
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.