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

Considere los métodos de extensión SingleOrDefault() y FirstOrDefault()

MSDN documenta que SingleOrDefault :

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

mientras que FirstOrDefault desde 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 sigues o sugieres al decidir usar SingleOrDefault() y FirstOrDefault() en tus consultas LINQ?

Siempre que use SingleOrDefault , debe SingleOrDefault claramente que la consulta debe dar como resultado un resultado único . Por otro lado, cuando se utiliza FirstOrDefault , la consulta puede devolver cualquier cantidad de resultados, pero usted indica que solo desea la primera.

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

Si su conjunto de resultados devuelve 0 registros:

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

Si el conjunto resultante arroja 1 registro:

  • SingleOrDefault devuelve ese registro
  • FirstOrDefault devuelve ese registro

Si su conjunto de resultados arroja muchos registros:

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

Conclusión:

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

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

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).
  • SingleOrDefault asume que hay un solo elemento y lo devuelve (o predeterminado si no existe ninguno). Los artículos múltiples son una violación del contrato, se emite una excepción.

Diferencia de rendimiento

  • FirstOrDefault es generalmente 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.

  • SingleOrDefault necesita comprobar 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

  • Use FirstOrDefault si no le importa cuántos artículos hay o si no puede permitirse comprobar la singularidad (por ejemplo, en una colección muy grande). Cuando verifique la exclusividad al agregar los elementos a la colección, podría ser demasiado costoso volver a verificarla al buscar esos elementos.

  • Use SingleOrDefault si no tiene que preocuparse demasiado por el rendimiento y quiere asegurarse de que la suposición de un solo elemento sea clara para el lector y verificada en tiempo de ejecución.

En la práctica, utiliza First / FirstOrDefault menudo incluso en casos en los que asume un solo elemento, para mejorar el rendimiento. Debería recordar que Single / SingleOrDefault puede mejorar la legibilidad (porque establece la suposición de un solo elemento) y la estabilidad (porque lo comprueba) y usarlo de manera adecuada.

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

Para LINQ -> SQL:

SingleOrDefault

  • generará una consulta como “select * from users where userid = 1”
  • Seleccionar registro coincidente, lanza una excepción si se encontraron más de un registro
  • Úselo si está obteniendo datos en base a la columna clave principal / única

FirstOrDefault

  • generará una consulta como “select top 1 * de usuarios donde userid = 1”
  • Seleccione las primeras filas que coinciden
  • Úselo si está obteniendo datos en base a una columna clave no primaria / única

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

SingleOrDefault: Usted está diciendo que “A lo sumo” hay un elemento que coincide con la consulta o por defecto FirstOrDefault: Usted está diciendo que hay “Al menos” un elemento que coincide con la consulta o por defecto

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

En sus casos, usaría lo siguiente:

select by ID == 5: está bien usar SingleOrDefault aquí, porque esperas una entidad [o ninguna], si tienes más de una entidad con ID 5, hay algo mal y definitivamente es digno de excepción.

cuando busques personas cuyo primer nombre sea igual a “Bobby”, puede haber más de uno (muy posiblemente yo pensaría), así que no debes usar Single ni First, solo selecciona con Where-operation (si “Bobby” devuelve demasiados entidades, el usuario debe 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 (no es probable que tenga una sola entidad, la ordenación no sería de mucha utilidad); sin embargo, esto significa que desea que TODAS las entidades estén ordenadas; si solo desea UNA, use FirstOrDefault, Soltero tiraría todas las veces si tienes más de una entidad.

En tu último ejemplo:

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

Sí lo hace. Si intenta utilizar SingleOrDefault() y los resultados de la consulta en más que el registro, obtendrá una excepción. La única vez que puede usar SingleOrDefault() manera segura es cuando espera solo 1 y solo 1 resultado …

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

 List items = new List() {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(); 

Entonces, según entiendo ahora, SingleOrDefault será bueno si está buscando datos que garanticen ser únicos, es decir, aplicados por restricciones de la base de datos, como la clave principal.

¿O hay una mejor forma de consultar la clave principal?

Suponiendo que mi TableAcc tiene

 AccountNumber - Primary Key, integer AccountName AccountOpenedDate AccountIsActive etc. 

y quiero consultar un número de cuenta AccountNumber 987654 , yo uso

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

Una cosa que se pierde en las respuestas …

Si hay resultados múltiples, FirstOrDefault sin un pedido puede traer resultados diferentes según la estrategia de índice utilizada por el servidor.

Personalmente no soporto ver FirstOrDefault en el código porque para mí dice que al desarrollador no le importaron los resultados. Sin embargo, con un pedido puede ser útil como una forma de aplicar lo último / lo más temprano posible. Tuve que corregir una gran cantidad de problemas causados ​​por desarrolladores descuidados que usan FirstOrDefault.

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

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