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 LastName
no comienzan en con "Ro"
--que pudieron se filtraron en el servidor.
@ 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
@ 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 –
@ 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