LINQ Simples vs Primeiro

214

LINQ:

É mais eficiente usar o Single()operador First()sempre que eu tiver certeza de que a consulta retornará um único registro ?

Existe alguma diferença?

Ian Vink
fonte

Respostas:

311

Se você espera um registro único, é sempre bom ser explícito no seu código.

Sei que outros escreveram por que você usa um ou outro, mas pensei em ilustrar por que você NÃO deveria usar um, quando quis dizer o outro.

Nota: No meu código, eu normalmente usar FirstOrDefault()e SingleOrDefault()mas isso é uma questão diferente.

Tome, por exemplo, uma tabela que armazena Customersem diferentes idiomas usando uma chave composta ( ID, Lang):

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

Este código acima apresenta um possível erro lógico (difícil de rastrear). Ele retornará mais de um registro (supondo que você tenha o registro do cliente em vários idiomas), mas sempre retornará apenas o primeiro ... que pode funcionar algumas vezes ... mas não em outros. É imprevisível.

Como sua intenção é retornar um Customeruso único Single();

O seguinte geraria uma exceção (que é o que você deseja neste caso):

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

Então, você simplesmente se bate na testa e diz para si mesmo ... OOPS! Eu esqueci o campo da linguagem! A seguir está a versão correta:

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

First() é útil no seguinte cenário:

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

Ele retornará UM objeto e, como você está usando a classificação, será o registro mais recente retornado.

Usar Single()quando você achar que ele deve retornar sempre 1 registro explicitamente ajudará a evitar erros de lógica.

Armstrongest
fonte
76
Os métodos Single e First podem usar um parâmetro de expressão para filtragem, portanto, a função Where não é necessária. Exemplo: Cliente do cliente = db.Customers.Single (c => c.ID == 5);
Josh Noe #
6
@ JoshNoe - Estou curioso, existe uma diferença entre customers.Where(predicate).Single() customers.Single(predicate)?
drzaus
9
@drzaus - Logicamente, não, os dois filtram os valores a serem retornados com base no predicado. No entanto, verifiquei a desmontagem e Where (predicado) .Single () tem três instruções extras no caso simples que fiz. Portanto, enquanto eu não sou especialista em IL, mas parece que o customers.Single (predicado) deve ser mais eficiente.
Josh Noe
5
@ JoshNoe, é exatamente o contrário.
AgentFire
10
@AgentFire Para fazer backup de sua declaração
M. Mimpen
72

Single lançará uma exceção se encontrar mais de um registro que corresponda aos critérios. Primeiro sempre selecionará o primeiro registro da lista. Se a consulta retornar apenas 1 registro, você poderá prosseguir First().

Ambos lançarão uma InvalidOperationExceptionexceção se a coleção estiver vazia. Alternativamente, você pode usar SingleOrDefault(). Isso não emitirá uma exceção se a lista estiver vazia

AlwaysAProgrammer
fonte
29

Solteiro()

Retorna um único elemento específico de uma consulta

Quando usar : se exatamente 1 elemento é esperado; não 0 ou mais que 1. Se a lista estiver vazia ou tiver mais de um elemento, será gerada uma exceção "Sequência contém mais de um elemento"

SingleOrDefault ()

Retorna um único elemento específico de uma consulta ou um valor padrão se nenhum resultado encontrado

Quando usar : quando são esperados 0 ou 1 elementos. Irá gerar uma exceção se a lista tiver 2 ou mais itens.

Primeiro()

Retorna o primeiro elemento de uma consulta com vários resultados.

Quando usar : quando 1 ou mais elementos são esperados e você deseja apenas o primeiro. Irá gerar uma exceção se a lista não contiver elementos.

FirstOrDefault ()

Retorna o primeiro elemento de uma lista com qualquer quantidade de elementos ou um valor padrão se a lista estiver vazia.

Quando usar : quando vários elementos são esperados e você deseja apenas o primeiro. Ou a lista está vazia e você deseja um valor padrão para o tipo especificado, o mesmo que default(MyObjectType). Por exemplo: se o tipo de lista for, list<int>ele retornará o primeiro número da lista ou 0 se a lista estiver vazia. Se for list<string>, retornará a primeira string da lista ou null se a lista estiver vazia.

Diego Mendes
fonte
1
Boa explicação. Eu mudaria apenas o que você pode usar Firstquando 1 ou mais elementos são esperados , não apenas "mais que 1" e FirstOrDefaultcom qualquer quantidade de elementos.
Andrew
18

Há uma diferença sutil e semântica entre esses dois métodos.

Use Singlepara recuperar o primeiro (e único) elemento de uma sequência que deve conter um elemento e não mais. Se a sequência tiver mais do que um elemento, sua chamada Singlecausará uma exceção, uma vez que você indicou que deveria haver apenas um elemento.

Use Firstpara recuperar o primeiro elemento de uma sequência que pode conter qualquer número de elementos. Se a sequência tiver mais do que um elemento, sua chamada Firstnão causará uma exceção, uma vez que você indicou que só precisa do primeiro elemento da sequência e não se importa se existir mais.

Se a sequência não contiver elementos, ambas as chamadas de método farão com que exceções sejam lançadas, pois os dois métodos esperam que pelo menos um elemento esteja presente.

Andrew Hare
fonte
17

Se você não deseja especificamente uma exceção lançada no caso de haver mais de um item, useFirst() .

Ambos são eficientes, pegue o primeiro item. First()é um pouco mais eficiente, porque não se preocupa em verificar se há um segundo item.

A única diferença é que Single()espera que exista apenas um item na enumeração e lançará uma exceção se houver mais de um. Você usa .Single() se deseja especificamente uma exceção lançada neste caso.

Patrick Karcher
fonte
14

Se bem me lembro, Single () verifica se existe outro elemento após o primeiro (e lança uma exceção, se for o caso), enquanto First () para após obtê-lo. Ambos lançam uma exceção se a sequência estiver vazia.

Pessoalmente, eu sempre uso First ().

Etienne de Martel
fonte
2
No SQL eles produzem First () faz TOP 1 e Single () faz TOP 2 se não me engano.
Matthijs Wessels
10

Com relação ao desempenho: um colega de trabalho e eu estávamos discutindo o desempenho do Single vs First (ou SingleOrDefault vs FirstOrDefault), e estava argumentando pelo ponto de que First (ou FirstOrDefault) seria mais rápido e melhoraria o desempenho (eu pretendo criar nosso aplicativo correr mais rápido).

Eu li vários posts no Stack Overflow que debatem isso. Alguns dizem que há pequenos ganhos de desempenho usando First em vez de Single. Isso ocorre porque o First retornaria o primeiro item, enquanto o Single deve varrer todos os resultados para garantir que não haja uma duplicata (ou seja: se ele encontrasse o item na primeira linha da tabela, ele ainda varria todas as outras linhas para verifique se não existe um segundo valor que corresponda à condição que, em seguida, geraria um erro). Eu senti como se estivesse em terreno sólido, com o “Primeiro” sendo mais rápido que o “Solteiro”, então decidi provar isso e encerrar o debate.

Configurei um teste no meu banco de dados e adicionei 1.000.000 de linhas de ID UniqueIdentifier Foreign UniqueIdentifier Info nvarchar (50) (preenchido com cadeias de números "0" a "999,9999"

Carreguei os dados e defini o ID como um campo de chave primária.

Usando o LinqPad, meu objetivo era mostrar que, se você pesquisasse um valor em 'Estrangeiro' ou 'Informações' usando Único, isso seria muito pior do que usar Primeiro.

Não sei explicar os resultados que obtive. Em quase todos os casos, o uso de Single ou SingleOrDefault era um pouco mais rápido. Isso não faz nenhum sentido lógico para mim, mas eu queria compartilhar isso.

Ex: usei as seguintes consultas:

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

Tentei consultas semelhantes no campo-chave 'Estrangeiro', que não estava indexado, pensando que o First seria mais rápido, mas o Single sempre foi um pouco mais rápido nos meus testes.

Marcus Talcott
fonte
2
Se estiver em um campo indexado, o banco de dados não precisará fazer uma verificação para garantir que seja exclusivo. Ele já sabe que é. Portanto, não há sobrecarga lá, e a única sobrecarga no lado do servidor é garantir que apenas um registro retorne. Fazendo testes de desempenho mim não é maneira conclusiva ou de outra
mirhagk
1
Eu não acho que esses resultados seriam os mesmos em um objeto complexo se você estivesse pesquisando em um campo não usado pelo IComparer.
Anthony Nichols
Essa deveria ser outra pergunta. Certifique-se de incluir a fonte do seu teste .
Sinatr 8/11
5

Eles são diferentes. Ambos afirmam que o conjunto de resultados não está vazio, mas único também afirma que não há mais de 1 resultado. Eu pessoalmente uso o Single nos casos em que espero apenas um resultado, apenas porque obter mais de um resultado é um erro e provavelmente deve ser tratado como tal.

jaltiere
fonte
5

Você pode tentar um exemplo simples para obter a diferença. A exceção será lançada na linha 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);
Rodion Chauskin
fonte
3

Muitas pessoas que conheço usam FirstOrDefault (), mas eu costumo usar SingleOrDefault () mais porque muitas vezes haveria algum tipo de inconsistência de dados se houvesse mais de um. Porém, isso está relacionado ao LINQ-to-Objects.

Matt H
fonte
-1

Os registros na entidade Funcionário:

Employeeid = 1: Apenas um funcionário com esse ID

Firstname = Robert: Mais de um funcionário com esse nome

Employeeid = 10: Nenhum funcionário com este ID

Agora é necessário entender o que Single()e o First()significado em detalhes.

Solteiro()

Single () é usado para retornar um único registro que existe exclusivamente em uma tabela; portanto, a consulta abaixo retornará o Funcionário cuja, employeed =1porque temos apenas um Funcionário cuja Employeedé 1. Se tivermos dois registros EmployeeId = 1, isso gera um erro (consulte a seção erro abaixo na segunda consulta para a qual estamos usando um exemplo Firstname.

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

O exemplo acima retornará um único registro, que possui 1 employeeId

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

O exemplo acima lançará uma exceção porque há registros múltiplos na tabela para FirstName='Robert'. A exceção será

InvalidOperationException: Sequence contém mais de um elemento

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

Isso, novamente, lançará uma exceção porque não existe registro para id = 10. A exceção será

InvalidOperationException: Sequence não contém elementos.

Pois EmployeeId = 10ele retornará nulo, mas como estamos usando Single(), gerará um erro. Para lidar com erro nulo, devemos usar SingleOrDefault().

Primeiro()

First () retorna de vários registros os registros correspondentes classificados em ordem crescente, de acordo com birthdateo que retornará 'Robert', que é o mais antigo.

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

Acima deve retornar o mais antigo, Robert conforme DOB.

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

Acima lançará uma exceção, pois não existe registro para id = 10. Para evitar uma exceção nula, devemos usar em FirstOrDefault()vez de First().

Nota: Podemos usar somente First()/ Single()quando tivermos certeza absoluta de que ele não pode retornar um valor nulo.

Nas duas funções, use SingleOrDefault () OR FirstOrDefault () que manipulará uma exceção nula; no caso de nenhum registro encontrado, ele retornará nulo.

Xerife
fonte
Por favor, explique sua resposta.
MrMaavin
1
@MrMaavin Eu atualizei, por favor, deixe-me saber agora é compreensível para você?
Xerife