2011-01-30 15 views
52

Me pregunto qué es lo diferente entre IQueryable, List, IEnumerator y cuándo debo usar cada uno?Diferencias entre IQueryable, List, IEnumerator?

Por ejemplo cuando se utiliza LINQ to SQL que haría algo como esto

public List<User> GetUsers() 
{ 
    return db.User.where(/* some query here */).ToList(); 
} 

ahora me pregunto si debo utilizar IQueryable lugar pero no estoy seguro de las ventajas de su uso sobre la lista.

Respuesta

89

IQueryable<T> está destinado a permitir que un proveedor de consultas (por ejemplo, un ORM como LINQ to SQL o Entity Framework) use las expresiones contenidas en una consulta para traducir la solicitud a otro formato. En otras palabras, LINQ-to-SQL examina las propiedades de las entidades que está utilizando junto con las comparaciones que está realizando y, de hecho, crea una declaración SQL para expresar (con suerte) una solicitud equivalente.

IEnumerable<T> es más genérico que IQueryable<T> (aunque todas las instancias de IQueryable<T> implementan IEnumerable<T>) y sólo define una secuencia. Sin embargo, hay métodos de extensión disponibles dentro de la clase Enumerable que definen algunos operadores de tipo consulta en esa interfaz y usan código ordinario para evaluar estas condiciones.

List<T> es solo un formato de salida, y si bien implementa IEnumerable<T>, no está directamente relacionado con las consultas.

En otras palabras, cuando usa IQueryable<T>, está definiendo y expresión que se traduce a algo más. Aunque está escribiendo código, ese código nunca obtiene ejecutado, solo obtiene inspeccionado y se convierte en otra cosa, como una consulta SQL real. Debido a esto, solo ciertas cosas son válidas dentro de estas expresiones. Por ejemplo, no puede llamar a una función común que defina desde estas expresiones, ya que LINQ-to-SQL no sabe cómo convertir su llamada en una declaración de SQL. La mayoría de estas restricciones solo se evalúan en tiempo de ejecución, desafortunadamente.

Cuando utiliza IEnumerable<T> para consultar, está utilizando LINQ-to-Objects, lo que significa que está escribiendo el código real que se utiliza para evaluar su consulta o transformar los resultados, por lo que, en general, no hay restricciones en lo que puedes hacer Puede llamar otras funciones desde dentro de estas expresiones libremente.

Con LINQ a SQL

ir mano a mano con la distinción anterior, también es importante tener en cuenta cómo funciona esto en la práctica. Cuando escribe una consulta en una clase de contexto de datos en LINQ to SQL, produce un IQueryable<T>. Hagas lo que hagas en contra del IQueryable<T> en sí se convertirá en SQL, por lo que tu filtrado y transformación se realizarán en el servidor. Lo que sea que haga contra este como IEnumerable<T>, se realizará a nivel de la aplicación. Algunas veces esto es deseable (en el caso de que necesite usar un código del lado del cliente, por ejemplo), pero en muchos casos esto no es intencional.

Por ejemplo, si yo tenía un contexto con una Customers propiedad que representa una tabla Customer, y cada cliente tiene una columna CustomerId, vamos a ver dos formas de hacer esta consulta:

var query = (from c in db.Customers where c.CustomerId == 5 select c).First(); 

Esto producirá SQL que consulta la base de datos para el registro Customer con un CustomerId igualando 5. Algo así como:

select CustomerId, FirstName, LastName from Customer where CustomerId = 5 

Ahora bien, ¿qué ocurre si nos volvemos Customers en un IEnumerable<Customer> usando el método de extensión AsEnumerable()?

var query = (from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c).First(); 

Este simple cambio tiene una consecuencia grave. Dado que estamos convirtiendo Customers en IEnumerable<Customer>, esto hará que toda la tabla regrese y la filtre en el lado del cliente (bueno, estrictamente hablando, esto traerá de vuelta cada fila en la tabla hasta que encuentre una que se ajuste a la crítica, pero el punto es el mismo).

ToList()

Hasta ahora, sólo hemos hablado de IQueryable y IEnumerable. Esto se debe a que son interfaces complementarias similares. En ambos casos, está definiendo una consulta ; es decir, está definiendo donde para encontrar los datos, qué filtros aplicar, y qué datos devolverá. Ambos son consultas

query = from c in db.Customers where c.CustomerId == 5 select c; 
query = from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c; 

Como ya hemos hablado, la primera consulta está utilizando IQueryable y la segunda utiliza IEnumerable. En ambos casos, sin embargo, esto es solo una consulta . Definir la consulta en realidad no hace nada en contra de la fuente de datos. La consulta se ejecuta realmente cuando el código comienza a iterar sobre la lista. Esto puede suceder de múltiples maneras; un bucle foreach, llamando ToList(), etc.

La consulta se ejecuta la primera y cada vez que se itera. Si tuviera que llamar al ToList() en query dos veces, terminaría con dos listas con objetos completamente distintos. Podrían contener los mismos datos, pero serían referencias diferentes.

Edición después de los comentarios

sólo quiero ser claro acerca de la distinción entre el momento de hacer las cosas del lado del cliente y cuando terminan del lado del servidor. Si hace referencia a IQueryable<T> como IEnumerable<T>, solo la consulta realizada después de es IEnumerable<T> se realizará en el lado del cliente. Por ejemplo, decir que tengo esta tabla y un contexto LINQ a SQL:

Customer 
----------- 
CustomerId 
FirstName 
LastName 

por primera vez construir una consulta basada en FirstName. Esto crea un IQueryable<Customer>:

var query = from c in db.Customers where c.FirstName.StartsWith("Ad") select c; 

Paso ahora que consulta a una función que toma un IEnumerable<Customer> y hace algún tipo de filtrado basado en LastName:

public void DoStuff(IEnumerable<Customer> customers) 
{ 
    foreach(var cust in from c in customers where c.LastName.StartsWith("Ro")) 
    { 
     Console.WriteLine(cust.CustomerId); 
    } 
} 

Hemos hecho una segunda consulta aquí, pero es hecho en un IEnumerable<Customer>. ¿Qué va a pasar aquí es que la primera consulta será evaluado, la ejecución de este SQL:

select CustomerId, FirstName, LastName from Customer where FirstName like 'Ad%' 

así que vamos a traer de vuelta a todos los que están FirstName se inicia con "Ad". Tenga en cuenta que no hay nada aquí acerca de LastName. Eso es porque está siendo filtrado por el lado del cliente.

Una vez que trae de vuelta estos resultados, el programa repetirá los resultados y solo entregará los registros cuyo LastName comience con "Ro". La desventaja de esto es que trajimos datos, es decir, todas las filas cuyo LastNameno comienzan en con "Ro" --que pudieron se filtraron en el servidor.

+0

@ Adam Robinson: Entonces, ¿es mejor permitirles obtener una consulta devuelta que todavía no ha llegado a la base de datos y cuando necesitan una lista, pueden repetirla (y luego acceden a la fuente de db).¿O es mejor simplemente darles una lista directa del bate? – chobo2

+1

@ chobo2: depende de lo que trates de hacer, por lo que no hay una respuesta correcta allí. En algunos casos, puede ser ventajoso permitir que el usuario refine aún más la consulta, especialmente en los casos en que puede tener una lógica de consulta compleja que sea común a varias consultas diferentes. Si bien esto no significa que deba * siempre * hacer esto, devolver 'IQueryable' con la lógica compleja base en su lugar le permitiría reutilizar esa lógica en diferentes lugares sin duplicarla en el código –

+0

@ Adam Robinson -Qué sucedería en este escenario Devuelvo una lista IQueryable pero mi vista necesita el uso de IEnumerator. Lo lanzaría a esto ¿habría algún problema con el que hablaste con Enumerable? – chobo2

8

IQueryable<T>: abstrae el acceso a la base de datos, admite la evaluación diferida de las consultas
List<T>: una colección de entradas. Sin el apoyo de la evaluación perezosa
IEnumerator<T>: proporciona la capacidad de una iteración y IEnumerable<T> (que tanto IQueryable<T> y List<T> son)

El problema con ese código es bastante simple - que siempre se ejecuta la consulta cuando se le llama. Si tuviera que devolver db.User.Where(...) en su lugar (que es IQueryable<T>), mantendría la evaluación de la consulta hasta que realmente se necesite (iterada). Además, si el usuario de ese método necesitara especificar más predicados, esos también se ejecutarán en la base de datos, lo que lo hace mucho más rápido.

+0

¿Qué clase de predicados adicionales está diciendo que dicen que hacen su cláusula Where que no afectan a la base de datos? Entonces, de esta manera, si necesitan decir filtrarlo aún más abajo, pueden hacerlo sin golpear el DB hasta que realices una iteración sobre él. – chobo2

+1

En general, un 'IQueryable ' solo se evalúa si se repite. Ahora bien, si uno fuera a encadenar otro 'Where (...)', también se ejecutaría en la base de datos. Con su código, se evaluaría en la memoria. Una base de datos está diseñada para realizar tales tareas y lo hará más rápido que en el filtrado de memoria. – Femaref

0

Utilice iList o List<item> cuando desee una colección fuertemente tipada de alguna entidad.

Y use Iqueryable y Ienumurator cuando desee obtener datos estúpidos como una colección de objetos, volverá a ser una colección de tipo suelto y no se aplicarán restricciones.

Preferiría usar List<type> porque al usar un wrap-up de lista y lanzar en una colección de tipo strong mi conjunto de resultados.

Además, el uso de una lista le dará la posibilidad de agregar, clasificar y convertir capas en Array, Ienumurator o como Queryable.