c# - ¿Diferencias entre IQueryable, List, IEnumerator?
linq ienumerable (3)
Use iList
o List<item>
cuando desee una colección fuertemente tipada de alguna entidad.
Y use Iqueryable
y Ienumurator
cuando desee obtener datos tontos como una colección de objetos, se volverá como una colección de tipo suelto y no se aplicarán restricciones.
Preferiría usar List<type>
porque usar un wrap-up de lista y lanzar en una colección de tipo strong mi conjunto de resultados.
Además, usar una lista le dará la posibilidad de agregar, clasificar y convertir capas en Array, Ienumurator o como Queryable.
Me pregunto qué es lo diferente entre IQueryable, List, IEnumerator y cuándo debo usar cada uno.
Por ejemplo, cuando se usa linq en sql, haría algo como esto
public List<User> GetUsers()
{
return db.User.where(/* some query here */).ToList();
}
ahora me pregunto si debería usar IQueryable en su lugar, pero no estoy seguro de las ventajas de usarlo en la lista.
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 soporte de evaluación perezosa
IEnumerator<T>
: proporciona la capacidad de iteración e IEnumerable<T>
(que tanto IQueryable<T>
como List<T>
son)
El problema con ese código es bastante simple: siempre ejecuta la consulta cuando se llama. Si devolviera db.User.Where(...)
lugar (que es un 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.
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 en 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 en realidad 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 solo 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 mientras implementa IEnumerable<T>
, no está directamente relacionado con la consulta.
En otras palabras, cuando usa IQueryable<T>
, está definiendo una expresión que se traduce a otra cosa. Aunque está escribiendo código, ese código nunca se ejecuta , solo se inspecciona 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
Yendo de la 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>
. Todo lo que haga contra el IQueryable<T>
sí mismo se convertirá en SQL, por lo que su filtrado y transformación se realizarán en el servidor. Cualquier cosa que haga contra esto como IEnumerable<T>
, se realizará a nivel de la aplicación. A veces esto es deseable (en el caso de que necesite utilizar un código del lado del cliente, por ejemplo), pero en muchos casos esto no es intencional.
Por ejemplo, si tuviera un contexto con una propiedad de Customers
representara una tabla Customer
, y cada cliente tiene una columna CustomerId
, veamos 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 del Customer
con un Id. De CustomerId
igual a 5. Algo así como:
select CustomerId, FirstName, LastName from Customer where CustomerId = 5
Ahora, ¿qué sucede si convertimos a Customers
en un IEnumerable<Customer>
de AsEnumerable()
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 grave consecuencia. Dado que convertimos a los Customers
en un IEnumerable<Customer>
, esto hará que toda la tabla regrese y la filtre en el lado del cliente (en rigor, esto traerá de vuelta cada fila de la tabla hasta que encuentre una que cumpla con los criterios , pero el punto es el mismo).
Listar()
Hasta ahora, solo hemos hablado de IEnumerable
e IQueryable
. Esto se debe a que son interfaces complementarias similares. En ambos casos, estás definiendo una consulta ; es decir, está definiendo dónde encontrar los datos, qué filtros aplicar y qué datos debe devolver. Ambas 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 hemos mencionado, la primera consulta usa IQueryable
y la segunda usa 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 a ToList()
, etc.
La consulta se ejecuta la primera y cada vez que se itera. Si ToList()
llamar a ToList()
en la query
dos veces, terminaría con dos listas con objetos completamente distintos. Podrían contener los mismos datos, pero serían referencias diferentes.
Editar después de los comentarios
Solo quiero ser claro acerca de la distinción entre cuando las cosas se hacen en el lado del cliente y cuando terminan en el lado del servidor. Si hace referencia a un IQueryable<T>
como un IEnumerable<T>
, solo se realizará la consulta después de que sea un IEnumerable<T>
del lado del cliente. Por ejemplo, supongamos que tengo esta tabla y un contexto LINQ-to-SQL:
Customer
-----------
CustomerId
FirstName
LastName
Primero construyo una consulta basada en FirstName
. Esto crea un IQueryable<Customer>
:
var query = from c in db.Customers where c.FirstName.StartsWith("Ad") select c;
Ahora paso esa consulta a una función que toma un IEnumerable<Customer>
y realiza algunos filtros basados 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 realizado una segunda consulta aquí, pero se está realizando en un IEnumerable<Customer>
. Lo que sucederá aquí es que la primera consulta será evaluada, ejecutando este SQL:
select CustomerId, FirstName, LastName from Customer where FirstName like ''Ad%''
Así que vamos a recuperar a todos los que FirstName
comienza 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 entregará solo los registros cuyo LastName
comienza con "Ro"
. La desventaja de esto es que recuperamos datos, es decir, todas las filas cuyo LastName
no comienza con "Ro"
pudieron haber sido filtradas en el servidor.