LINQ Single vs Primero

215

LINQ:

¿Es más eficiente usar el Single()operador First()cuando sé con certeza que la consulta devolverá un solo registro ?

¿Hay una diferencia?

Ian Vink
fuente

Respuestas:

312

Si espera un registro único, siempre es bueno ser explícito en su código.

Sé que otros han escrito por qué usas uno u otro, pero pensé que ilustraría por qué NO deberías usar uno, cuando te refieres al otro.

Nota: En mi código, normalmente usaré FirstOrDefault()y SingleOrDefault()esa es una pregunta diferente.

Tome, por ejemplo, una tabla que se almacena Customersen diferentes idiomas usando una clave compuesta ( ID, Lang):

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

Este código anterior introduce un posible error lógico (difícil de rastrear). Devolverá más de un registro (suponiendo que tenga el registro del cliente en varios idiomas) pero siempre devolverá solo el primero ... que puede funcionar a veces ... pero no en otros. Es impredecible

Dado que su intención es devolver un Customeruso único Single();

Lo siguiente arrojaría una excepción (que es lo que quiere en este caso):

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

Luego, simplemente te golpeas en la frente y te dices a ti mismo ... ¡Vaya! ¡Olvidé el campo del idioma! La siguiente es la versión correcta:

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

First() es útil en el siguiente escenario:

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

Devolverá UN objeto, y dado que está utilizando la clasificación, será el registro más reciente que se devuelva.

Usarlo Single()cuando sienta que debe devolver explícitamente siempre 1 registro lo ayudará a evitar errores lógicos.

Armstrongest
fuente
76
Los métodos Single y First pueden tomar un parámetro de expresión para el filtrado, por lo que la función Where no es necesaria. Ejemplo: Cliente cliente = db.Customers.Single (c => c.ID == 5);
Josh Noe
66
@ JoshNoe - Tengo curiosidad, ¿hay alguna diferencia entre customers.Where(predicate).Single() customers.Single(predicate)?
drzaus
99
@drzaus: lógicamente, no, ambos filtran los valores que se devolverán en función del predicado. Sin embargo, revisé el desensamblaje, y Where (predicate) .Single () tiene tres instrucciones adicionales en el caso simple que hice. Entonces, aunque no soy un experto en IL, pero parece que los clientes. Single (predicado) debería ser más eficiente.
Josh Noe
55
@JoshNoe es todo lo contrario, como resultado.
AgentFire
10
@AgentFire Para respaldar su declaración
M. Mimpen
72

Single lanzará una excepción si encuentra más de un registro que coincida con los criterios. Primero siempre seleccionará el primer registro de la lista. Si la consulta devuelve solo 1 registro, puede ir conFirst() .

Ambos lanzarán una InvalidOperationExceptionexcepción si la colección está vacía. Alternativamente puedes usar SingleOrDefault(). Esto no arrojará una excepción si la lista está vacía

AlwaysAProgrammer
fuente
29

Soltero()

Devuelve un único elemento específico de una consulta.

Cuándo usar : si se espera exactamente 1 elemento; no 0 o más de 1. Si la lista está vacía o tiene más de un elemento, arrojará una Excepción "La secuencia contiene más de un elemento"

SingleOrDefault ()

Devuelve un único elemento específico de una consulta o un valor predeterminado si no se encuentra ningún resultado

Cuando se usa : cuando se esperan 0 o 1 elementos. Lanzará una excepción si la lista tiene 2 o más elementos.

Primero()

Devuelve el primer elemento de una consulta con múltiples resultados.

Cuando se usa : cuando se esperan 1 o más elementos y solo quieres el primero. Lanzará una excepción si la lista no contiene elementos.

FirstOrDefault ()

Devuelve el primer elemento de una lista con cualquier cantidad de elementos, o un valor predeterminado si la lista está vacía.

Cuándo usar : cuando se esperan varios elementos y solo quieres el primero. O la lista está vacía y desea un valor predeterminado para el tipo especificado, igual que default(MyObjectType). Por ejemplo: si el tipo de lista es list<int>, devolverá el primer número de la lista o 0 si la lista está vacía. Si es list<string>así, devolverá la primera cadena de la lista o nula si la lista está vacía.

Diego Mendes
fuente
1
Buena explicación Solo cambiaría lo que puede usar Firstcuando se esperan 1 o más elementos , no solo "más de 1" y FirstOrDefaultcon cualquier cantidad de elementos.
Andrew
18

Hay una sutil diferencia semántica entre estos dos métodos.

Utilícelo Singlepara recuperar el primer (y único) elemento de una secuencia que debe contener un elemento y no más. Si la secuencia tiene más de un elemento, su invocación deSingle provocará una excepción ya que usted indicó que solo debería haber un elemento.

Utilícelo Firstpara recuperar el primer elemento de una secuencia que puede contener cualquier número de elementos. Si la secuencia tiene más de un elemento, su invocación deFirst , no provocará una excepción, ya que indicó que solo necesita el primer elemento de la secuencia y no le importa si existen más.

Si la secuencia no contiene elementos, ambas llamadas a métodos provocarán excepciones, ya que ambos métodos esperan que al menos un elemento esté presente.

Andrew Hare
fuente
17

Si no desea que se arroje una excepción específicamente en caso de que haya más de un elemento, úseloFirst() .

Ambos son eficientes, toma el primer artículo. First()es un poco más eficiente porque no se molesta en verificar si hay un segundo elemento.

La única diferencia es que Single()espera que solo haya un elemento en la enumeración, y arrojará una excepción si hay más de uno. Se usa .Single() si desea específicamente que se arroje una excepción en este caso.

Patrick Karcher
fuente
14

Si recuerdo, Single () comprueba si hay otro elemento después del primero (y lanza una excepción si es el caso), mientras que First () se detiene después de obtenerlo. Ambos lanzan una excepción si la secuencia está vacía.

Personalmente, siempre uso Primero ().

Etienne de Martel
fuente
2
En el SQL, producen First () hace TOP 1 y Single () hace TOP 2 si no me equivoco.
Matthijs Wessels
10

Con respecto al rendimiento: un compañero de trabajo y yo estábamos discutiendo el rendimiento de Single vs First (o SingleOrDefault vs FirstOrDefault), y estaba argumentando que First (o FirstOrDefault) sería más rápido y mejoraría el rendimiento (estoy a punto de crear nuestra aplicación corre más rápido).

He leído varias publicaciones en Stack Overflow que debaten esto. Algunos dicen que hay pequeñas ganancias de rendimiento con First en lugar de Single. Esto se debe a que Primero simplemente devolverá el primer elemento, mientras que Single debe escanear todos los resultados para asegurarse de que no haya un duplicado (es decir: si encuentra el elemento en la primera fila de la tabla, aún escaneará cada dos filas para asegúrese de que no haya un segundo valor que coincida con la condición que luego arrojaría un error). Sentí que estaba en tierra firme con "Primero" siendo más rápido que "Soltero", así que me propuse probarlo y dejar el debate.

Configuré una prueba en mi base de datos y agregué 1,000,000 de filas de ID UniqueIdentifier Foreign UniqueIdentifier Info nvarchar (50) (lleno de cadenas de números "0" a "999,9999"

Cargué los datos y configuré la ID como un campo de clave principal.

Con LinqPad, mi objetivo era mostrar que si buscaba un valor en 'Extranjero' o 'Información' usando Single, sería mucho peor que usar First.

No puedo explicar los resultados que obtuve. En casi todos los casos, usar Single o SingleOrDefault fue un poco más rápido. Esto no tiene ningún sentido lógico para mí, pero quería compartirlo.

Ej: utilicé las siguientes 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)

Intenté consultas similares en el campo clave 'Extranjero' que no estaba indexado pensando que demostraría que Primero es más rápido, pero Single siempre fue un poco más rápido en mis pruebas.

Marcus Talcott
fuente
2
Si está en un campo indexado, la base de datos no necesita hacer un análisis para garantizar que sea único. Ya sabe que lo es. Por lo tanto, no hay sobrecarga allí, y la única sobrecarga en el lado del servidor es garantizar que solo regrese un registro. Hacer pruebas de rendimiento por mí mismo no es concluyente de una forma u otra
mirhagk
1
No creo que estos resultados sean los mismos en un objeto complejo si estuviera buscando en un campo no utilizado por IComparer.
Anthony Nichols
Esa debería ser otra pregunta. Asegúrese de incluir la fuente de su prueba .
Sinatr
5

Ellos son diferentes. Ambos afirman que el conjunto de resultados no está vacío, pero solo también afirma que no hay más de 1 resultado. Personalmente uso Single en los casos en que solo espero que haya 1 resultado solo porque obtener más de 1 resultado es un error y probablemente debería tratarse como tal.

jaltiere
fuente
5

Puedes probar un ejemplo simple para obtener la diferencia. Se lanzará una excepción en la línea 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);
Chauskin Rodion
fuente
3

Mucha gente que conozco usa FirstOrDefault (), pero tiendo a usar SingleOrDefault () más porque a menudo sería algún tipo de inconsistencia de datos si hubiera más de uno. Sin embargo, se trata de LINQ-to-Objects.

Matt H
fuente
-1

Los registros en la entidad Empleado:

Employeeid = 1: Solo un empleado con esta identificación

Firstname = Robert: Más de un empleado con este nombre

Employeeid = 10: Ningún empleado con esta identificación

Ahora es necesario entender qué Single()y First()significar en detalle.

Soltero()

Single () se utiliza para devolver un único registro que existe de forma única en una tabla, por lo que la consulta a continuación devolverá el Empleado cuyo employeed =1porque tenemos solo un Empleado cuyo Employeedes 1. Si tenemos dos registros para EmployeeId = 1entonces arroja un error (vea el error a continuación en la segunda consulta donde estamos usando un ejemplo para Firstname.

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

Lo anterior devolverá un solo registro, que tiene 1 employeeId

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

Lo anterior arrojará una excepción porque los registros múltiples están en la tabla para FirstName='Robert'. La excepción será

InvalidOperationException: la secuencia contiene más de un elemento

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

Esto, nuevamente, arrojará una excepción porque no existe un registro para id = 10. La excepción será

InvalidOperationException: la secuencia no contiene elementos.

Porque EmployeeId = 10devolverá nulo, pero a medida que Single()lo usemos arrojará un error. Para manejar el error nulo debemos usarSingleOrDefault() .

Primero()

Primero () devuelve de múltiples registros los registros correspondientes ordenados en orden ascendente de acuerdo con birthdatelo que devolverá 'Robert', que es el más antiguo.

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

Arriba debería devolver el más antiguo, Robert según DOB.

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

Lo anterior arrojará una excepción ya que no existe un registro para id = 10. Para evitar una excepción nula, deberíamos usar en FirstOrDefault()lugar deFirst() .

Nota: Solo podemos usar First()/Single() cuando estemos absolutamente seguros de que no puede devolver un valor nulo.

En ambas funciones, use SingleOrDefault () OR FirstOrDefault () que manejará una excepción nula, en el caso de que no se encuentre ningún registro, devolverá nulo.

Alguacil
fuente
Por favor explique su respuesta.
MrMaavin
1
@ MrMaavin He actualizado, por favor, hágamelo saber, ¿es comprensible ahora para usted?
Sheriff