LINQ: quando usar SingleOrDefault vs. FirstOrDefault () com critérios de filtragem


506

Considere os métodos de extensão IEnumerable SingleOrDefault()eFirstOrDefault()

Documentos MSDN queSingleOrDefault :

Retorna o único elemento de uma sequência ou um valor padrão se a sequência estiver vazia; esse método lança uma exceção se houver mais de um elemento na sequência.

enquanto que FirstOrDefaulta partir da MSDN (presumivelmente quando se utiliza um OrderBy()ou OrderByDescending()ou nenhuma),

Retorna o primeiro elemento de uma sequência

Considere algumas consultas de exemplo, nem sempre é claro quando usar esses dois métodos:

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?

Questão

Quais convenções você segue ou sugere ao decidir usar SingleOrDefault()e FirstOrDefault()em suas consultas LINQ?

Respostas:


466

Sempre que você usa SingleOrDefault, você afirma claramente que a consulta deve resultar em no máximo um único resultado. Por outro lado, quando FirstOrDefaulté usada, a consulta pode retornar qualquer quantidade de resultados, mas você declara que deseja apenas o primeiro.

Pessoalmente, acho a semântica muito diferente e usar a apropriada, dependendo dos resultados esperados, melhora a legibilidade.


164
Uma diferença muito importante é que, se você usar o SingleOrDefault em uma sequência com mais de um elemento, ele lançará uma exceção.
Kamran Bigdely

17
@kami, se não lançasse uma exceção, seria exatamente como FirstOrDefault. A exceção é o que o torna SingleOrDefault. Bom ponto de vista, e coloca um prego no caixão das diferenças.
Fabio S.

17
Devo dizer que, do ponto de vista do desempenho, o FirstOrDefault está trabalhando cerca de 10 vezes mais rápido que o SingleOrDefault, usando uma Lista <MyClass> de 9.000.000 elementos, a classe contém 2 números inteiros e o Func contém uma pesquisa desses dois números inteiros. A pesquisa de 200 vezes em um loop levou 22 segundos em var v = list.SingleOrDefault (x => x.Id1 == i && x.Id2 == i); e var v = list.FirstOrDefault (x => x.Id1 == i && x.Id2 == i); cerca de 3 segundos
Chen

6
@BitsandBytesHandyman Se SignleOrDefault não lançasse uma exceção quando a sequência contivesse mais de um item, ela não se comportaria exatamente como FirstOrDefault. FirstOrDefault retorna o primeiro item, ou null se a sequência estiver vazia. SingleOrDefault deve retornar o único item ou null se a sequência estiver vazia OU se contiver mais de um item, sem gerar uma exceção.
Thanasis Ioannidis

2
@RSW Sim, estou ciente disso. Lendo atentamente meu comentário, eu estava dizendo o que o SingleOrDefault deveria fazer, não o que ele faz. Mas é claro que o que deveria fazer é muito subjetivo. Para mim, o padrão "SomethingOrDefault" significa: Obtenha o valor de "Something". Se "Algo" falhar ao retornar um valor, retorne o valor padrão. O que significa que o valor padrão deve ser retornado mesmo no caso em que "Something" geraria uma exceção. Portanto, onde Single lançaria uma exceção, SingleOrDefault deve retornar o valor padrão, na minha perspectiva.
Thanasis Ioannidis

585

Se o seu conjunto de resultados retornar 0 registros:

  • SingleOrDefault retorna o valor padrão para o tipo (por exemplo, o padrão para int é 0)
  • FirstOrDefault retorna o valor padrão para o tipo

Se o conjunto de resultados retornar 1 registro:

  • SingleOrDefault retorna esse registro
  • FirstOrDefault retorna esse registro

Se o seu conjunto de resultados retornar muitos registros:

  • SingleOrDefault lança uma exceção
  • FirstOrDefault retorna o primeiro registro

Conclusão:

Se você deseja que uma exceção seja lançada se o conjunto de resultados contiver muitos registros, use SingleOrDefault.

Se você deseja sempre 1 registro, independentemente do conjunto de resultados, use FirstOrDefault


6
Eu sugeriria que é raro realmente querer a exceção, portanto, na maioria das vezes, FirstOrDefault seria preferido. Eu sei que os casos existiriam apenas não muito frequentemente.
MikeKulls

FirstOrDefaulté retornado o primeiro registro significa novo registro (último) / registro antigo (primeiro)? u pode me esclarecer?
Duk

@ Duk, depende de como você classifica os registros. Você pode usar OrderBy () ou OrderByDescending () etc antes de chamar FirstOrDefault. Veja o exemplo de código do OP.
Gan

5
Eu também gosto dessa resposta. Especialmente porque há algumas ocasiões em que você realmente deseja que a exceção seja lançada porque pretende lidar com esse caso raro adequadamente em outro lugar, em vez de apenas fingir que isso não acontece. Quando você deseja a exceção, está dizendo isso claramente e também forçando outras pessoas a lidar apenas com o processo de tornar o sistema geral mais robusto.
Francis Rodgers

É muito claramente afirmado para que se possa entender facilmente.
Nirav Vasoya 17/04/19

244

Há sim

  • uma diferença semântica
  • uma diferença de desempenho

entre os dois.

Diferença semântica:

  • FirstOrDefault retorna um primeiro item de potencialmente múltiplo (ou padrão, se não houver nenhum).
  • SingleOrDefaultassume que existe um único item e o devolve (ou o padrão, se não houver). Vários itens são uma violação do contrato, uma exceção é lançada.

Diferença de desempenho

  • FirstOrDefaultgeralmente é mais rápido, itera até encontrar o elemento e precisa iterar todo o enumerável quando não o encontra. Em muitos casos, há uma alta probabilidade de encontrar um item.

  • SingleOrDefaultprecisa verificar se existe apenas um elemento e, portanto, sempre itera todo o enumerável. Para ser preciso, ele itera até encontrar um segundo elemento e gerar uma exceção. Mas na maioria dos casos, não há segundo elemento.

Conclusão

  • Use FirstOrDefaultse você não se importa com quantos itens existem ou quando não pode verificar a exclusividade (por exemplo, em uma coleção muito grande). Quando você verifica a exclusividade ao adicionar os itens à coleção, pode ser muito caro verificá-lo novamente ao procurar esses itens.

  • Use SingleOrDefaultse você não precisa se preocupar muito com o desempenho e deseja garantir que a suposição de um único item seja clara para o leitor e verificada em tempo de execução.

Na prática, você usa First/ FirstOrDefaultfrequentemente mesmo nos casos em que assume um único item, para melhorar o desempenho. Você ainda deve se lembrar que Single/ SingleOrDefaultpode melhorar a legibilidade (porque afirma a suposição de um único item) e a estabilidade (porque a verifica) e usá-lo adequadamente.


16
+1 "ou quando você não puder verificar a exclusividade (por exemplo, em uma coleção muito grande)." . Eu estava procurando por isso. Eu também adicionaria aplicar a exclusividade ao inserir, ou / e por design, em vez de no momento da consulta!
Nawaz

Eu posso imaginar a iteração SingleOrDefaultde vários objetos ao usar o Linq to Objects, mas não SingleOrDefaultprecisa iterar no máximo 2 itens se o Linq estiver falando com um banco de dados, por exemplo? Basta saber ..
Memet Olsen

3
@memetolsen Considere o espeto código para os dois com LINQ to SQL - FirstOrDefault usa Top 1. SingleOrDefault usa Top 2.
Jim Wooley

@ JimWooley Acho que não entendi a palavra 'enumerável'. Eu pensei que Stefan quis dizer o C # Enumerable.
Meme Olsen

1
@memetolsen correto em termos da resposta original, seu comentário estava se referindo ao banco de dados, então eu estava oferecendo o que acontece com o provedor. Enquanto o código .Net itera apenas mais de 2 valores, o banco de dados visita quantos registros forem necessários até atingir o segundo que atenda aos critérios.
Jim Wooley

76

Ninguém mencionou que o FirstOrDefault traduzido no SQL faz o registro TOP 1 e o SingleOrDefault faz o TOP 2, porque é necessário saber se há mais de um registro.


3
Quando executei um SingleOrDefault através do LinqPad e do VS, nunca obtive o SELECT TOP 2, com o FirstOrDefault consegui o SELECT TOP 1, mas, tanto quanto posso dizer, você não obtém o SELECT TOP 2.
Jamie R Rytlewski

Ei, eu já tentei no linqpad também e consulta sql me deixou com medo, porque buscar completamente todas as linhas. Não sei como isso pode acontecer?
AnyOne 23/03

1
Isso depende inteiramente do provedor LINQ usado. Por exemplo, LINQ to SQL e LINQ to Entities podem ser traduzidos para SQL de diferentes maneiras. Eu apenas tentei o LINQPad com o provedor IQ MySql e FirstOrDefault()adiciona LIMIT 0,1enquanto SingleOrDefault()nada acrescenta.
Lucas

1
EF Núcleo 2.1 traduz FirstOrDefault para selecionar TOP (1), SingleOrDefault para selecionar TOP (2)
camainc

19

Para LINQ -> SQL:

SingleOrDefault

  • gerará uma consulta como "selecione * de usuários em que userid = 1"
  • Selecionar registro correspondente, gera exceção se mais de um registro encontrado
  • Use se você estiver buscando dados com base na coluna de chave primária / exclusiva

FirstOrDefault

  • gerará uma consulta como "selecione os principais 1 * dos usuários em que userid = 1"
  • Selecione as primeiras linhas correspondentes
  • Use se você estiver buscando dados com base na coluna de chave não primária / exclusiva

eu acho que você deve remover "Selecione todas as linhas correspondentes" de SingleOrDefault
Saim Abdullah

10

Eu uso SingleOrDefaultem situações em que minha lógica determina que o resultado será zero ou um. Se houver mais, é uma situação de erro, o que é útil.


3
Muitas vezes, acho que SingleOrDefault () destaca casos em que não apliquei a filtragem correta no conjunto de resultados ou em que há um problema com duplicações nos dados subjacentes. Mais frequentemente, eu me vejo usando Single () e SingleOrDefault () sobre os métodos First ().
TimS 16/11/12

Há implicações de desempenho para Single () e SingleOrDefault () no LINQ to Objects se você tiver um grande enumerável, mas ao falar com um banco de dados (por exemplo, SQL Server), ele fará uma chamada de top 2 e se seus índices estiverem configurados corretamente, a chamada não deve ser cara e eu prefiro falhar rapidamente e encontrar o problema de dados em vez de possivelmente introduzir outros problemas de dados, pegando a duplicata errada ao chamar First () ou FirstOrDefault ().
21815 heartlandcoder

5

SingleOrDefault: você está dizendo que "No máximo" há um item que corresponde à consulta ou padrão FirstOrDefault: Você está dizendo que há "Pelo menos" um item que corresponde à consulta ou padrão

Diga isso em voz alta na próxima vez que precisar escolher e provavelmente deverá escolher sabiamente. :)


5
Na verdade, não ter resultados é um uso perfeitamente aceitável de FirstOrDefault. More correctly: FirstOrDefault` = Qualquer número de resultados, mas eu me preocupo apenas com o primeiro, também pode não haver resultados. SingleOrDefault= Existem 1 ou 0 resultados, se houver mais, significa que há um erro em algum lugar. First= Há pelo menos um resultado e eu quero. Single= Existe exatamente 1 resultado, nem mais, nem menos, e eu quero esse.
precisa saber é o seguinte

4

Nos seus casos, eu usaria o seguinte:

selecione por ID == 5: não há problema em usar SingleOrDefault aqui, porque você espera uma entidade [ou nenhuma], se você tiver mais de uma entidade com o ID 5, há algo errado e definitivamente uma exceção digna.

ao procurar pessoas cujo primeiro nome seja igual a "Bobby", pode haver mais de um (possivelmente eu pensaria); portanto, você não deve usar Único nem Primeiro, basta selecionar com a operação Onde (se "Bobby" retornar muitos entidades, o usuário precisa refinar sua pesquisa ou escolher um dos resultados retornados)

a ordem pela data de criação também deve ser executada com uma operação Where (é improvável que tenha apenas uma entidade, a classificação não seria muito útil;) no entanto, isso implica que você deseja que TODAS as entidades sejam classificadas - se você quiser apenas UMA, use FirstOrDefault, Single jogaria toda vez se você tivesse mais de uma entidade.


3
Discordo. Se o seu ID do banco de dados for uma chave primária, o banco de dados já estará aplicando exclusividade. Desperdiçar o ciclo da CPU para verificar se o banco de dados está fazendo seu trabalho em todas as consultas é simplesmente bobo.
John Henckel

4

Ambos são os operadores do elemento e são usados ​​para selecionar um único elemento de uma sequência. Mas há uma pequena diferença entre eles. O operador SingleOrDefault () lançaria uma exceção se mais de um elemento fosse atendido, a condição em que FirstOrDefault () não lançará nenhuma exceção para o mesmo. Aqui está o exemplo.

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
"há uma pequena diferença entre eles" - Isso é importante!
Nikhil Vartak

3

No seu último exemplo:

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

Sim. Se você tentar usar SingleOrDefault()e a consulta resultar em mais do que registro, você obteria uma exceção. O único momento em que você pode usar com segurança SingleOrDefault()é quando espera apenas 1 e apenas 1 resultado ...


Está correto. Se você obtiver 0 resultado, também receberá uma exceção.
Dennis Rongo

1

Então, como eu entendo agora, SingleOrDefaultserá bom se você estiver consultando dados que são garantidos como únicos, ou seja, aplicados por restrições de banco de dados, como chave primária.

Ou existe uma maneira melhor de consultar a chave primária.

Supondo que minha TableAcc tenha

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

e eu quero consultar um AccountNumber 987654, eu uso

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

1

Na minha opinião, FirstOrDefaultestá sendo muito usado em demasia. Na maioria dos casos, ao filtrar dados, você espera recuperar uma coleção de elementos que correspondam à condição lógica ou um único elemento único por seu identificador exclusivo - como usuário, livro, publicação etc. por que podemos chegar ao ponto de dizer que FirstOrDefault()é um cheiro de código, não porque há algo errado com ele, mas porque está sendo usado com muita frequência. Esta postagem do blog explora o tópico em detalhes. Na maioria das vezes, a IMO SingleOrDefault()é uma alternativa muito melhor; portanto, preste atenção a esse erro e use o método mais apropriado que represente claramente seu contrato e expectativas.


-1

Uma coisa que está faltando nas respostas ....

Se houver vários resultados, o FirstOrDefault sem um pedido de pode trazer de volta resultados diferentes com base em qual estratégia de índice já foi usada pelo servidor.

Pessoalmente, não suporto ver FirstOrDefault no código porque, para mim, o desenvolvedor não se importava com os resultados. Com um pedido, ele pode ser útil como uma maneira de aplicar o mais recente / o mais antigo. Eu tive que corrigir muitos problemas causados ​​por desenvolvedores descuidados usando o FirstOrDefault.


-2

Consultei o Google sobre o uso dos diferentes métodos no GitHub. Isso é feito executando uma consulta de pesquisa do Google para cada método e limitando a consulta ao domínio github.com e à extensão de arquivo .cs usando a consulta "site: arquivo github.com: cs ..."

Parece que os métodos First * são mais comumente usados ​​que os métodos Single *.

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

-8

Não entendo por que você está usando FirstOrDefault(x=> x.ID == key)quando isso pode recuperar resultados muito mais rapidamente se você usar Find(key). Se você estiver consultando com a chave Primária da tabela, a regra geral é sempre usar Find(key). FirstOrDefaultdeve ser usado para coisas predicadas como (x=> x.Username == username)etc.

isso não mereceu voto negativo, pois o cabeçalho da pergunta não era específico para linq no DB ou Linq to List / IEnumerable etc.


1
Em que espaço de nome está Find()?
precisa saber é o seguinte

Você poderia nos dizer por favor? Ainda esperando por uma resposta.
Denny


A palavra "IEnumerable" está na primeira linha do corpo da pergunta. Se você apenas leu o título, e não a pergunta real, e postou uma resposta incorreta como resultado, esse é o seu erro e uma razão perfeitamente legítima para rebaixar a IMO.
F1Krazy 22/01/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.