LINQ: когда использовать SingleOrDefault или FirstOrDefault () с критериями фильтрации

506

Рассмотрим методы расширения IEnumerable 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?

p.campbell
источник

Ответы:

466

Всякий раз, когда вы используете SingleOrDefault, вы четко заявляете, что запрос должен привести не более одного результата. С другой стороны, при FirstOrDefaultиспользовании запрос может возвращать любое количество результатов, но вы утверждаете, что вам нужен только первый.

Я лично нахожу семантику очень разной, и использование соответствующей, в зависимости от ожидаемых результатов, улучшает читабельность.

Брайан Менард
источник
164
Очень важным отличием является то, что если вы используете SingleOrDefault для последовательности с более чем одним элементом, он вызывает исключение.
Kamran Bigdely
17
@kami, если бы оно не выдало исключение, это было бы точно так же, как FirstOrDefault. Исключением является то, что делает его SingleOrDefault. Хорошая мысль поднять это и положить гвоздь на гроб разногласий.
Фабио С.
17
Я должен сказать, что с точки зрения производительности FirstOrDefault работает примерно в 10 раз быстрее, чем SingleOrDefault, используя List <MyClass> из 9 000 000 элементов, класс содержит 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 возвращает первый элемент или ноль, если последовательность пуста. SingleOrDefault должен возвращать единственный элемент или NULL, если последовательность пуста, ИЛИ, если она содержит более одного элемента, без исключения вообще.
Танасис Иоаннидис
2
@RSW Да, я знаю об этом. Внимательно читая мой комментарий, я говорил, что должен делать SingleOrDefault, а не то, что он делает. Но, конечно, то, что он должен делать, очень субъективно. Для меня шаблон «SomethingOrDefault» означает: получить значение «Something». Если «Something» не может вернуть значение, верните значение по умолчанию. Это означает, что значение по умолчанию должно быть возвращено даже в случае, когда «Something» выдает исключение. Поэтому, если Single генерирует исключение, SingleOrDefault должен возвращать значение по умолчанию, на мой взгляд.
Танасис Иоаннидис
585

Если ваш набор результатов возвращает 0 записей:

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

Если ваш набор результатов возвращает 1 запись:

  • SingleOrDefault возвращает эту запись
  • FirstOrDefault возвращает эту запись

Если ваш набор результатов возвращает много записей:

  • SingleOrDefault бросает исключение
  • FirstOrDefault возвращает первую запись

Вывод:

Если вы хотите, чтобы генерировалось исключение, если результирующий набор содержит много записей, используйте SingleOrDefault.

Если вам всегда нужна 1 запись, независимо от того, что содержится в наборе результатов, используйте FirstOrDefault

Alex
источник
6
Я хотел бы предположить, что на самом деле редко требуется исключение, поэтому большую часть времени предпочтение будет отдано FirstOrDefault. Я знаю, что случаи бывали, но не так часто.
MikeKulls
FirstOrDefaultвозвращается первая запись означает новую запись (последнюю) / старую запись (первую)? Вы можете уточнить меня?
Дук
@Duk, это зависит от того, как вы сортируете записи. Вы можете использовать OrderBy () или OrderByDescending () и т. Д. Перед вызовом FirstOrDefault. Смотрите пример кода ОП.
Ган
5
Мне тоже нравится этот ответ. Особенно если учесть, что в некоторых случаях вы действительно хотите, чтобы было сгенерировано исключение, потому что вы намерены правильно обработать этот редкий случай в другом месте, а не просто притворяться, что этого не происходит. Когда вы хотите получить исключение, вы говорите это ясно, а также заставляете других справляться, просто делая всю систему более устойчивой.
Фрэнсис Роджерс
Это очень ясно сказано, так что можно легко понять.
Нирав Васоя
244

Там есть

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

между двумя.

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

  • FirstOrDefault возвращает первый элемент из потенциально нескольких (или по умолчанию, если ни один не существует).
  • SingleOrDefaultПредполагается, что существует один элемент, и возвращает его (или по умолчанию, если его нет). Несколько предметов являются нарушением договора, исключение выдается.

Разница в производительности

  • FirstOrDefaultобычно быстрее, он выполняет итерацию до тех пор, пока не найдет элемент, и должен выполнять итерацию всего перечисляемого, только когда не находит его. Во многих случаях существует высокая вероятность найти предмет.

  • SingleOrDefaultНеобходимо проверить, существует ли только один элемент и, следовательно, всегда повторять все перечисляемое. Чтобы быть точным, он повторяется до тех пор, пока не найдет второй элемент и не выдаст исключение. Но в большинстве случаев второго элемента нет.

Вывод

  • Используйте, FirstOrDefaultесли вам все равно, сколько там предметов или когда вы не можете позволить себе проверить уникальность (например, в очень большой коллекции). Когда вы проверяете уникальность при добавлении предметов в коллекцию, может быть слишком дорого проверять это снова при поиске этих предметов.

  • Используйте, SingleOrDefaultесли вам не нужно слишком сильно заботиться о производительности и хотите убедиться, что предположение об одном элементе понятно читателю и проверено во время выполнения.

На практике вы используете First/ FirstOrDefaultчасто даже в тех случаях, когда вы принимаете один элемент, для повышения производительности. Вы все равно должны помнить, что Single/ SingleOrDefaultможет улучшить читабельность (потому что в нем говорится о единственном элементе) и стабильность (потому что он проверяет его) и использовать его соответствующим образом.

Стефан Штайнеггер
источник
16
+1 "или когда вы не можете позволить себе проверить уникальность (например, в очень большой коллекции)." , Я искал это. Я бы также добавил принудительную уникальность при вставке или / и дизайном, а не во время выполнения запроса!
Наваз
Я могу себе представить, что SingleOrDefaultпри использовании Linq to Objects выполняется итерация по большому количеству объектов, но не SingleOrDefaultнужно ли повторять не более двух элементов, если Linq, например, обращается к базе данных? Просто интересно ..
Мемет Олсен
3
@memetolsen Рассмотрим кодовый сплит для двух с LINQ to SQL - FirstOrDefault использует Top 1. SingleOrDefault использует Top 2.
Джим Вули
@JimWooley Полагаю, я неправильно понял слово «перечислимый». Я думал, что Стефан имел в виду C # Enumerable.
Мемет Олсен
1
@memetolsen правильно с точки зрения исходного ответа, ваш комментарий имел отношение к базе данных, поэтому я предлагал, что происходит от поставщика. В то время как .Net-код повторяет только 2 значения, база данных посещает столько записей, сколько необходимо, пока не достигнет второй, соответствующей критериям.
Джим Вули
76

Никто не упомянул, что FirstOrDefault, переведенный в SQL, делает TOP 1 запись, а SingleOrDefault TOP 2, потому что нужно знать, есть ли более 1 запись.

Shalke
источник
3
Когда я запускал SingleOrDefault через LinqPad и VS, я никогда не получал SELECT TOP 2, с FirstOrDefault я смог получить SELECT TOP 1, но, насколько я могу судить, вы не получите SELECT TOP 2.
Джейми Р Ритлевски,
Привет, меня тоже опробовали в linqpad, и SQL-запрос заставил меня бояться, потому что он полностью выбирает все строки. Я не уверен, как это может произойти?
AnyOne
1
Это полностью зависит от используемого поставщика LINQ. Например, LINQ to SQL и LINQ to Entities могут переводиться в 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

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

FirstOrDefault

  • сгенерирует запрос типа «выберите топ 1 * из пользователей, где userid = 1»
  • Выберите первые подходящие строки
  • Используйте, если вы выбираете данные на основе столбца с не первичным / уникальным ключом
Пракаш
источник
я думаю, что вы должны удалить «Выбрать все подходящие строки» из SingleOrDefault
Саим Абдулла
10

Я использую SingleOrDefaultв ситуациях, когда моя логика диктует, что будет либо ноль, либо один результат. Если их больше, это ошибочная ситуация, что полезно.

расточитель
источник
3
Часто я обнаруживаю, что SingleOrDefault () выделяет случаи, когда я не применил правильную фильтрацию к набору результатов, или где есть проблема с дублированием в базовых данных. Чаще всего я использую Single () и SingleOrDefault () вместо методов First ().
TimS
Существуют ограничения производительности для Single () и SingleOrDefault () в LINQ to Objects, если у вас большой перечислимый объект, но при обращении к базе данных (например, SQL Server) он будет выполнять вызов top 2, и если ваши индексы настроены правильно, вызов не должен быть дорогим, и я бы предпочел быстро потерпеть неудачу и найти проблему с данными, вместо того, чтобы, возможно, представлять другие проблемы с данными, используя неправильный дубликат при вызове First () или FirstOrDefault ().
Heartlandcoder
5

SingleOrDefault: вы говорите, что «Максимум» есть один элемент, соответствующий запросу или по умолчанию FirstOrDefault: Вы говорите, что «По крайней мере» один элемент соответствует запросу или по умолчанию

Скажите это вслух в следующий раз, когда вам нужно будет выбрать, и вы, скорее всего, сделаете мудрый выбор. :)

Стивен
источник
5
На самом деле отсутствие результатов - это вполне приемлемое использование FirstOrDefault. More correctly: FirstOrDefault` = любое количество результатов, но я забочусь только о первом, результатов также может не быть. SingleOrDefault= Есть 1 или 0 результатов, если больше, значит, где-то есть ошибка. First= Есть хотя бы один результат, и я этого хочу. Single= Есть ровно 1 результат, не больше, не меньше, и я хочу этого.
Davy8
4

В ваших случаях я бы использовал следующее:

выберите по ID == 5: здесь можно использовать SingleOrDefault, потому что вы ожидаете одну [или ни одну] сущность, если вы получили более одной сущности с идентификатором 5, что-то не так и определенно заслуживает исключения.

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

упорядочение по дате создания также должно выполняться с помощью операции Where (маловероятно, что она будет иметь только одну сущность, сортировка не будет иметь большого значения;) это, однако, подразумевает, что вы хотите отсортировать ВСЕ сущности - если вы хотите просто ОДИН, используйте 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 результат ...

bytebender
источник
Это правильно. Если вы получите 0 результат, вы также получите исключение.
Деннис Ронго
1

Итак, как я понимаю сейчас, SingleOrDefault будет хорошо, если вы запрашиваете данные, которые гарантированно будут уникальными, то есть обеспечены ограничениями БД, такими как первичный ключ.

Или есть лучший способ запроса первичного ключа.

Предполагая, что мой TableAcc имеет

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

и я хочу запросить AccountNumber 987654, я использую

var data = datacontext.TableAcc.FirstOrDefault(obj => obj.AccountNumber == 987654);
MG
источник
1

По моему мнению, FirstOrDefaultэто слишком злоупотребляют. В большинстве случаев, когда вы фильтруете данные, вы ожидаете получить коллекцию элементов, соответствующих логическому условию, или один уникальный элемент по его уникальному идентификатору, такой как пользователь, книга, публикация и т. Д. почему мы можем даже сказать, что FirstOrDefault()это запах кода не потому, что с ним что-то не так, а потому, что он используется слишком часто. Этот пост в блоге исследует тему в деталях. ИМО в большинстве случаев SingleOrDefault()является гораздо лучшей альтернативой, поэтому следите за этой ошибкой и убедитесь, что вы используете наиболее подходящий метод, который четко отражает ваш контракт и ожидания.

Василь Костурский
источник
-1

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

Если есть несколько результатов, FirstOrDefault без заказа by может вернуть разные результаты, основанные на том, какая серверная стратегия использовалась когда-либо.

Лично я не могу видеть 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 на List / IEnumerable и т. д.

Терон Говендер
источник
1
В каком пространстве имен Find()?
p.campbell
Не могли бы вы сказать нам, пожалуйста? Все еще жду ответа.
Денни
Может быть так: stackoverflow.com/questions/14032709/…
Jeppe
Слово «IEnumerable» находится в самой первой строке тела вопроса. Если вы только прочитали заголовок, а не реальный вопрос, и в результате разместили неверный ответ, то это ваша ошибка и вполне законная причина для отрицания IMO.
F1Krazy