LINQ сингл против первого


216

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

Затем вы просто бьете себя по лбу и говорите себе ... ОЙ! Я забыл поле языка! Ниже приводится правильная версия:

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 не обязательна. Пример: Customer customer = db.Customers.Single (c => c.ID == 5);
Джош Но

6
@JoshNoe - мне любопытно, есть ли разница между customers.Where(predicate).Single() customers.Single(predicate)?
drzaus

9
@drzaus - логически, нет, они оба фильтруют значения, которые должны быть возвращены на основе предиката. Тем не менее, я проверил разборку, и Where (предикат) .Single () имеет три дополнительные инструкции в простом случае, который я сделал. Итак, пока я не эксперт по IL, но похоже, что клиенты. Single (предикат) должны быть более эффективными.
Джош Ноу

5
@JoshNoe это как раз наоборот.
AgentFire


72

Single сгенерирует исключение, если найдет более одной записи, соответствующей критериям. Первая всегда выберет первую запись из списка. Если запрос возвращает только 1 запись, вы можете пойти с First().

Оба будут генерировать InvalidOperationExceptionисключение, если коллекция пуста. В качестве альтернативы вы можете использовать SingleOrDefault(). Это не вызовет исключения, если список пуст


30

Не замужем()

Возвращает один конкретный элемент запроса

Когда используется : если ожидается ровно 1 элемент; не 0 или больше 1. Если список пуст или содержит более одного элемента, он выдаст исключение «Последовательность содержит более одного элемента»

SingleOrDefault ()

Возвращает один конкретный элемент запроса или значение по умолчанию, если результат не найден

Когда используется : когда ожидается 0 или 1 элемент. Он выдаст исключение, если список содержит 2 или более элементов.

Первый()

Возвращает первый элемент запроса с несколькими результатами.

Когда используется : когда ожидается 1 или более элементов, и вы хотите только первый. Он выдаст исключение, если список не содержит элементов.

FirstOrDefault ()

Возвращает первый элемент списка с любым количеством элементов или значение по умолчанию, если список пуст.

Когда используется : когда ожидается несколько элементов, и вы хотите только первый. Или список пуст, и вы хотите значение по умолчанию для указанного типа, так же, как default(MyObjectType). Например: если указан тип списка, list<int>он вернет первое число из списка или 0, если список пуст. Если это так list<string>, он вернет первую строку из списка или ноль, если список пуст.


1
Хорошее объяснение. Я хотел бы изменить только то, что вы можете использовать, Firstкогда ожидается 1 или более элементов , а не только «более 1» и FirstOrDefaultс любым количеством элементов.
Эндрю

18

Между этими двумя методами есть тонкая семантическая разница.

Используется Singleдля извлечения первого (и единственного) элемента из последовательности, которая должна содержать один элемент и не более. Если в последовательности содержится больше одного элемента, ваш вызов Singleвызовет исключение, поскольку вы указали, что должен быть только один элемент.

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

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


18

Если вы не хотите специально создавать исключение в случае наличия более одного элемента, используйтеFirst() .

Оба эффективны, возьмите первый пункт. First()немного более эффективен, потому что он не проверяет наличие второго элемента.

Единственное отличие состоит в том, Single()что в начале перечисления будет только один элемент, и будет выдано исключение, если их несколько. Вы используете, .Single() если вы хотите, чтобы в этом случае было сгенерировано исключение .


14

Если я помню, Single () проверяет, есть ли другой элемент после первого (и выдает исключение, если это так), тогда как First () останавливается после его получения. Оба выдают исключение, если последовательность пуста.

Лично я всегда использую First ().


2
В SQL они выдают First () делает TOP 1, а Single () делает TOP 2, если я не ошибаюсь.
Маттис Вессельс

10

Что касается производительности: мы с коллегой обсуждали производительность Single vs First (или SingleOrDefault vs FirstOrDefault), и я спорил о том, что First (или FirstOrDefault) будет быстрее и улучшит производительность (я все о создании нашего приложения бежать быстрее).

Я прочитал несколько сообщений о переполнении стека, которые обсуждают это. Некоторые говорят, что при использовании First вместо Single наблюдается небольшой прирост производительности. Это связано с тем, что First просто вернет первый элемент, в то время как Single должен просканировать все результаты, чтобы убедиться в отсутствии дубликата (т. Е. Если он найдет элемент в первой строке таблицы, он все равно будет сканировать все остальные строки, чтобы убедитесь, что нет второго значения, соответствующего условию, которое затем выдаст ошибку). Я чувствовал, что был на твердой почве, когда «First» был быстрее, чем «Single», поэтому я решил доказать это и положить конец дискуссии.

Я настроил тест в своей базе данных и добавил 1 000 000 строк ID UniqueIdentifier Foreign UniqueIdentifier Info nvarchar (50) (заполненный строками с номерами от «0» до «999,9999»)

Я загрузил данные и установил ID в качестве поля первичного ключа.

Используя 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)

Я пробовал похожие запросы в поле 'Foreign' key, которое не было проиндексировано, полагая, что First быстрее, но Single всегда немного быстрее в моих тестах.


2
Если оно находится в индексированном поле, то базе данных не нужно выполнять сканирование, чтобы убедиться, что оно уникально. Уже известно, что это так. Таким образом, нет никаких накладных расходов, и единственные накладные расходы на стороне сервера гарантируют, что вернулась только одна запись. Проводить тесты производительности самому, это не является окончательным, так или иначе
mirhagk

1
Я не думаю, что эти результаты были бы одинаковыми для сложного объекта, если бы вы искали в поле, не используемом IComparer.
Энтони Николс

Это должен быть другой вопрос. Не забудьте указать источник вашего теста .
Синатр

5

Они разные. Оба они утверждают, что набор результатов не является пустым, но одиночный также утверждает, что существует не более 1 результата. Я лично использую Single в тех случаях, когда я ожидаю, что будет только 1 результат только потому, что получение более 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-to-Objects.


-1

Записи в сущности Сотрудник:

Employeeid = 1: Только один сотрудник с этим идентификатором

Firstname = Robert: Более одного сотрудника с таким именем

Employeeid = 10: Нет сотрудника с таким ID

Теперь необходимо понять, что Single()и что First()значить в деталях.

Не замужем()

Single () используется для возврата единственной записи, которая уникально существует в таблице, поэтому следующий запрос вернет сотрудника, employeed =1у которого только один сотрудник Employeedравен 1. Если у нас есть две записи, 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() .

Первый()

First () возвращает из нескольких записей соответствующие записи, отсортированные в порядке возрастания в соответствии с birthdateтем, что он возвращает «Роберт», который является самым старым.

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

Выше должен вернуть самый старый, Роберт согласно ДОБ.

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

Выше будет выдано исключение, так как не существует записи для id = 10. Чтобы избежать нулевого исключения, мы должны использоватьFirstOrDefault() вместоFirst() .

Примечание: мы можем использовать только First()/Single() когда мы абсолютно уверены, что он не может вернуть нулевое значение.

В обеих функциях используйте SingleOrDefault () ИЛИ FirstOrDefault (), который будет обрабатывать нулевое исключение, в случае отсутствия записи он вернет ноль.


Пожалуйста, объясните свой ответ.
MrMaavin

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