LINQ: Cuándo usar SingleOrDefault vs. FirstOrDefault () con criterios de filtrado

506

Considere los métodos de extensión IEnumerable SingleOrDefault()yFirstOrDefault()

MSDN documenta queSingleOrDefault :

Devuelve el único elemento de una secuencia, o un valor predeterminado si la secuencia está vacía; Este método genera una excepción si hay más de un elemento en la secuencia.

mientras que FirstOrDefaulta partir de MSDN (presumiblemente cuando se utiliza un OrderBy()o OrderByDescending()o ninguno en absoluto),

Devuelve el primer elemento de una secuencia.

Considere un puñado de consultas de ejemplo, no siempre está claro cuándo usar estos dos 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?

Pregunta

¿Qué convenciones sigue o sugiere cuando decide usar SingleOrDefault()y FirstOrDefault()en sus consultas LINQ?

p.campbell
fuente

Respuestas:

466

Cada vez que usa SingleOrDefault, declara claramente que la consulta debe dar como resultado un único resultado. Por otro lado, cuando FirstOrDefaultse usa, la consulta puede devolver cualquier cantidad de resultados, pero usted declara que solo desea el primero.

Personalmente, considero que la semántica es muy diferente y usar la apropiada, dependiendo de los resultados esperados, mejora la legibilidad.

Bryan Menard
fuente
164
Una diferencia muy importante es que si usa SingleOrDefault en una secuencia con más de un elemento, arroja una excepción.
Kamran Bigdely
17
@kami si no arrojara una excepción, sería exactamente como FirstOrDefault. La excepción es lo que lo hace SingleOrDefault. Buen punto mencionarlo, y pone un clavo en el ataúd de las diferencias.
Fabio S.
17
Debo decir que, desde el punto de vista del rendimiento, FirstOrDefault funciona aproximadamente 10 veces más rápido que SingleOrDefault, utilizando una Lista <MyClass> de 9,000,000 elementos, la clase contiene 2 enteros y el Func contiene una búsqueda de estos dos enteros. Buscar 200 veces en un ciclo tomó 22 segundos en var v = list.SingleOrDefault (x => x.Id1 == i && x.Id2 == i); y var v = list.FirstOrDefault (x => x.Id1 == i && x.Id2 == i); aproximadamente 3 segundos
Chen
66
@BitsandBytesHandyman Si SignleOrDefault no arrojó una excepción cuando la secuencia contenía más de un elemento, no se comportaría exactamente como FirstOrDefault. FirstOrDefault devuelve el primer elemento, o nulo si la secuencia está vacía. SingleOrDefault debería devolver el único elemento, o nulo si la secuencia está vacía O si contiene más de un elemento, sin lanzar una excepción en absoluto.
Thanasis Ioannidis
2
@RSW Sí, soy consciente de eso. Leyendo cuidadosamente mi comentario, decía lo que SingleOrDefault debería hacer, no lo que hace. Pero, por supuesto, lo que debe hacer es muy subjetivo. Para mí, el patrón "SomethingOrDefault" significa: Obtener el valor de "Something". Si "Algo" no puede devolver un valor, devuelva el valor predeterminado. Lo que significa que el valor predeterminado debe devolverse incluso en el caso de que "Algo" arroje una excepción. Entonces, donde Single arrojaría una excepción, SingleOrDefault debería devolver el valor predeterminado, en mi perspectiva.
Thanasis Ioannidis
585

Si su conjunto de resultados devuelve 0 registros:

  • SingleOrDefault devuelve el valor predeterminado para el tipo (por ejemplo, el valor predeterminado para int es 0)
  • FirstOrDefault devuelve el valor predeterminado para el tipo

Si el conjunto de resultados devuelve 1 registro:

  • SingleOrDefault devuelve ese registro
  • FirstOrDefault devuelve ese registro

Si su conjunto de resultados devuelve muchos registros:

  • SingleOrDefault lanza una excepción
  • FirstOrDefault devuelve el primer registro

Conclusión:

Si desea que se produzca una excepción si el conjunto de resultados contiene muchos registros, use SingleOrDefault.

Si siempre desea 1 registro sin importar el contenido del conjunto de resultados, use FirstOrDefault

Alex
fuente
66
Sugeriría que es raro querer realmente la excepción, por lo que la mayoría de las veces se preferiría FirstOrDefault. Sé que los casos existirían, pero no tan a menudo.
MikeKulls
FirstOrDefaultse devuelve primer registro significa nuevo registro (último) / antiguo registro (primero) ¿me puede aclarar?
Duk
@Duk, depende de cómo ordenar los registros. Puede usar OrderBy () o OrderByDescending (), etc. antes de llamar a FirstOrDefault. Vea el ejemplo de código del OP.
Gan
55
Me gusta esta respuesta también. Especialmente dado que hay algunas ocasiones en las que realmente desea que se lance la excepción porque tiene la intención de manejar ese caso raro en otro lugar en lugar de simplemente pretender que no sucede. Cuando desea la excepción, está diciendo esto claramente, y también obliga a otros a manejar solo el encapsulado, lo que hace que el sistema en general sea más robusto.
Francis Rodgers
Está muy claramente establecido para que uno pueda entender fácilmente.
Nirav Vasoya
244

Ahi esta

  • una diferencia semántica
  • una diferencia de rendimiento

entre los dos.

Diferencia Semántica:

  • FirstOrDefault devuelve un primer elemento potencialmente múltiple (o predeterminado si no existe ninguno).
  • SingleOrDefaultasume que hay un solo elemento y lo devuelve (o predeterminado si no existe ninguno). Múltiples artículos son una violación del contrato, se lanza una excepción.

Diferencia de rendimiento

  • FirstOrDefaultgeneralmente es más rápido, itera hasta que encuentra el elemento y solo tiene que iterar todo el enumerable cuando no lo encuentra. En muchos casos, hay una alta probabilidad de encontrar un artículo.

  • SingleOrDefaultnecesita verificar si solo hay un elemento y, por lo tanto, siempre itera todo el enumerable. Para ser precisos, itera hasta que encuentra un segundo elemento y arroja una excepción. Pero en la mayoría de los casos, no hay un segundo elemento.

Conclusión

  • Úselo FirstOrDefaultsi no le importa cuántos elementos hay o cuándo no puede permitirse el lujo de comprobar la unicidad (por ejemplo, en una colección muy grande). Cuando verifica la unicidad al agregar los elementos a la colección, puede ser demasiado costoso volver a verificarlo al buscar esos elementos.

  • Úselo SingleOrDefaultsi no tiene que preocuparse demasiado por el rendimiento y desea asegurarse de que la suposición de un solo elemento sea clara para el lector y se verifique en tiempo de ejecución.

En la práctica, usas First/ a FirstOrDefaultmenudo, incluso en los casos en que asumes un solo elemento, para mejorar el rendimiento. Aún debe recordar que Single/ SingleOrDefaultpuede mejorar la legibilidad (porque establece la suposición de un solo elemento) y la estabilidad (porque lo comprueba) y usarlo adecuadamente.

Stefan Steinegger
fuente
16
+1 "o cuando no puede permitirse el lujo de comprobar la unicidad (por ejemplo, en una colección muy grande)". . Yo estaba buscando esto. ¡También agregaría imponer unicidad al insertar, o / y por diseño en lugar de al momento de hacer la consulta!
Nawaz
Me puedo imaginar SingleOrDefaultiterar sobre muchos objetos al usar Linq to Objects, pero no SingleOrDefaulttiene que iterar como máximo 2 elementos si Linq está hablando con una base de datos, por ejemplo. Me pregunto ...
Memet Olsen
3
@memetolsen Considere el código spit para los dos con LINQ to SQL: FirstOrDefault usa Top 1. SingleOrDefault usa Top 2.
Jim Wooley
@JimWooley Creo que entendí mal la palabra 'enumerable'. Pensé que Stefan se refería a C # Enumerable.
Memet Olsen
1
@memetolsen correcto en términos de la respuesta original, su comentario se refería a la base de datos, por lo que estaba ofreciendo lo que sucede del proveedor. Mientras que el código .Net solo itera sobre 2 valores, la base de datos visita tantos registros como sea necesario hasta que alcanza el segundo que cumple los criterios.
Jim Wooley
76

Nadie ha mencionado que FirstOrDefault traducido en SQL registra TOP 1, y SingleOrDefault registra TOP 2, porque necesita saber si hay más de 1 registro.

shalke
fuente
3
Cuando ejecuté un SingleOrDefault a través de LinqPad y VS, nunca obtuve SELECT TOP 2, con FirstOrDefault pude obtener SELECT TOP 1, pero por lo que puedo decir, no obtengo SELECT TOP 2.
Jamie R Rytlewski
Hola, también me han probado en linqpad y la consulta sql me asustó porque recupera completamente todas las filas. No estoy seguro de cómo puede suceder esto.
AnyOne
1
Esto depende completamente del proveedor LINQ utilizado. Por ejemplo, LINQ to SQL y LINQ to Entities podrían traducirse a SQL de diferentes maneras. Acabo de probar LINQPad con el proveedor IQ MySql, y FirstOrDefault()agrega LIMIT 0,1mientras SingleOrDefault()agrega nada.
Lucas
1
EF Core 2.1 traduce FirstOrDefault a SELECT TOP (1), SingleOrDefault a SELECT TOP (2)
camainc el
19

Para LINQ -> SQL:

SingleOrDefault

  • generará una consulta como "select * from users where userid = 1"
  • Seleccione un registro coincidente, genera una excepción si se encuentran más de un registro
  • Úselo si está obteniendo datos basados ​​en la columna de clave principal / única

FirstOrDefault

  • generará una consulta como "seleccionar top 1 * de los usuarios donde userid = 1"
  • Seleccione las primeras filas coincidentes
  • Úselo si está buscando datos basados ​​en una columna de clave única / no primaria
Prakash
fuente
Creo que debería eliminar "Seleccionar todas las filas coincidentes" de SingleOrDefault
Saim Abdullah
10

Lo uso SingleOrDefaulten situaciones donde mi lógica dicta que el resultado será cero o uno. Si hay más, es una situación de error, lo cual es útil.

gastador
fuente
3
A menudo encuentro que SingleOrDefault () resalta casos en los que no he aplicado el filtrado correcto en el conjunto de resultados, o donde hay un problema con las duplicaciones en los datos subyacentes. La mayoría de las veces me encuentro usando Single () y SingleOrDefault () sobre los métodos First ().
TimS
Hay implicaciones de rendimiento para Single () y SingleOrDefault () en LINQ to Objects si tiene un gran número enumerable pero cuando habla con una base de datos (por ejemplo, SQL Server) hará una llamada a los 2 principales y si sus índices están configurados correctamente, la llamada no debería ser costosa y preferiría fallar rápidamente y encontrar el problema de datos en lugar de posiblemente introducir otros problemas de datos al tomar el duplicado incorrecto al llamar a First () o FirstOrDefault ().
heartlandcoder
5

SingleOrDefault: Estás diciendo que "Como máximo" hay un elemento que coincide con la consulta o predeterminado FirstOrDefault: Estás diciendo que hay "Al menos" un elemento que coincide con la consulta o predeterminado

Dígalo en voz alta la próxima vez que necesite elegir y probablemente elija sabiamente. :)

Steven
fuente
55
En realidad, no tener resultados es un uso perfectamente aceptable de FirstOrDefault. More correctly: FirstOrDefault` = Cualquier número de resultados, pero solo me importa el primero, también puede que no haya resultados. SingleOrDefault= Hay 1 o 0 resultados, si hay más, eso significa que hay un error en alguna parte. First= Hay al menos un resultado, y lo quiero. Single= Hay exactamente 1 resultado, ni más, ni menos, y quiero ese.
Davy8
4

En sus casos, usaría lo siguiente:

seleccione por ID == 5: está bien usar SingleOrDefault aquí, porque espera una entidad [o ninguna], si tiene más de una entidad con ID 5, hay algo mal y definitivamente una excepción digna.

al buscar personas cuyo nombre sea igual a "Bobby", puede haber más de uno (posiblemente creo), por lo que no debe usar Single ni First, solo seleccione con la operación Where (si "Bobby" devuelve demasiados entidades, el usuario tiene que refinar su búsqueda o elegir uno de los resultados devueltos)

el orden por fecha de creación también debe realizarse con una operación Where (es poco probable que solo tenga una entidad, la clasificación no sería de mucha utilidad); sin embargo, esto implica que desea ordenar TODAS las entidades; si solo desea UNA, use FirstOrDefault, Solo tiraría cada vez si tienes más de una entidad.

Olli
fuente
3
Estoy en desacuerdo. Si su ID de base de datos es una clave principal, entonces la base de datos ya está aplicando unicidad. Perder el ciclo de la CPU para verificar si la base de datos está haciendo su trabajo en cada consulta es una tontería.
John Henckel
4

Ambos son operadores de elementos y se utilizan para seleccionar un solo elemento de una secuencia. Pero hay una pequeña diferencia entre ellos. El operador SingleOrDefault () lanzaría una excepción si se satisface más de un elemento, la condición donde FirstOrDefault () no arrojará ninguna excepción para el mismo. Aquí está el ejemplo.

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();
Sheo Dayal Singh
fuente
2
"hay una pequeña diferencia entre ellos" - ¡Eso es importante!
Nikhil Vartak
3

En tu último ejemplo:

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

Si lo hace Si intenta usar SingleOrDefault()y la consulta da como resultado más que un registro, obtendrá una excepción. El único momento que puede usar de forma segura SingleOrDefault()es cuando espera solo 1 y solo 1 resultado ...

bytebender
fuente
Eso es correcto. Si obtienes 0 resultados, también obtienes una excepción.
Dennis Rongo
1

Entonces, según tengo entendido ahora, SingleOrDefaultserá bueno si está buscando datos que garanticen que sean únicos, es decir, forzados por restricciones de DB como la clave primaria.

¿O hay una mejor manera de consultar la clave primaria?

Suponiendo que mi TableAcc tiene

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

y quiero consultar un AccountNumber 987654, yo uso

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

En mi opinión FirstOrDefaultse está usando demasiado. En la mayoría de los casos, cuando está filtrando datos, esperaría recuperar una colección de elementos que coinciden con la condición lógica o un único elemento único por su identificador único, como un usuario, libro, publicación, etc. por qué incluso podemos llegar a decir que FirstOrDefault()es un olor a código no porque haya algo mal con él sino porque se está usando con demasiada frecuencia. Esta publicación de blog explora el tema en detalle. La OMI la mayoría de las veces SingleOrDefault()es una alternativa mucho mejor, así que tenga cuidado con este error y asegúrese de utilizar el método más apropiado que represente claramente su contrato y sus expectativas.

Vasil Kosturski
fuente
-1

Una cosa que se pierde en las respuestas ...

Si hay varios resultados, FirstOrDefault sin un pedido puede devolver resultados diferentes en función de la estrategia de índice utilizada por el servidor.

Personalmente, no puedo soportar ver FirstOrDefault en el código porque para mí dice que al desarrollador no le importaron los resultados. Con un pedido por, puede ser útil como una forma de hacer cumplir lo último / más temprano. He tenido que corregir muchos problemas causados ​​por desarrolladores descuidados que usan FirstOrDefault.

El tipo
fuente
-2

Le pregunté a Google por el uso de los diferentes métodos en GitHub. Esto se realiza ejecutando una consulta de búsqueda de Google para cada método y limitando la consulta al dominio github.com y la extensión de archivo .cs mediante la consulta "sitio: archivo github.com: cs ..."

Parece que los métodos First * se usan más comúnmente que los métodos Single *.

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

No entiendo por qué estás usando FirstOrDefault(x=> x.ID == key)cuando esto podría recuperar resultados mucho más rápido si lo usas Find(key). Si está consultando con la clave principal de la tabla, la regla general es usar siempre Find(key). FirstOrDefaultdebe usarse para cosas predicadas como (x=> x.Username == username)etc.

esto no merecía un voto negativo ya que el encabezado de la pregunta no era específico de linq en DB o Linq to List / IEnumerable, etc.

Theron Govender
fuente
1
¿En qué espacio de nombres está Find()?
p.campbell
¿Podrías decirnos por favor? Todavía estoy esperando una respuesta.
Denny
La palabra "IEnumerable" está en la primera línea del cuerpo de la pregunta. Si solo leyó el título y no la pregunta real, y como resultado publicó una respuesta incorrecta, ese es su error y una razón perfectamente legítima para rechazar la OMI.
F1Krazy