¿De qué sirve IQueryable
en el contexto de LINQ?
¿Se utiliza para desarrollar métodos de extensión o para cualquier otro propósito?
¿De qué sirve IQueryable
en el contexto de LINQ?
¿Se utiliza para desarrollar métodos de extensión o para cualquier otro propósito?
Respuestas:
La respuesta de Marc Gravell es muy completa, pero también pensé en agregar algo sobre esto desde el punto de vista del usuario ...
La principal diferencia, desde la perspectiva del usuario, es que, cuando usa IQueryable<T>
(con un proveedor que admite las cosas correctamente), puede ahorrar muchos recursos.
Por ejemplo, si está trabajando en una base de datos remota, con muchos sistemas ORM, tiene la opción de obtener datos de una tabla de dos maneras, una que devuelve IEnumerable<T>
y otra que devuelve un IQueryable<T>
. Digamos, por ejemplo, que tiene una tabla de Productos y desea obtener todos los productos cuyo costo es> $ 25.
Si lo haces:
IEnumerable<Product> products = myORM.GetProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Lo que sucede aquí es que la base de datos carga todos los productos y los pasa a través del cable a su programa. Su programa luego filtra los datos. En esencia, la base de datos hace un SELECT * FROM Products
, y le devuelve CADA producto.
Con el IQueryable<T>
proveedor adecuado , por otro lado, puede hacer:
IQueryable<Product> products = myORM.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
El código se ve igual, pero la diferencia aquí es que el SQL ejecutado lo será SELECT * FROM Products WHERE Cost >= 25
.
Desde su POV como desarrollador, esto se ve igual. Sin embargo, desde el punto de vista del rendimiento, solo puede devolver 2 registros a través de la red en lugar de 20,000 ...
IQueryable<Product>
- sería específico para su ORM o repositorio, etc.
foreach
o llame ToList()
), en realidad no golpea la base de datos.
En esencia, su trabajo es muy similar a IEnumerable<T>
- para representar una fuente de datos consultable - la diferencia es que los diversos métodos LINQ (on Queryable
) pueden ser más específicos, para construir la consulta usando Expression
árboles en lugar de delegados (que es lo que Enumerable
usa).
El proveedor LINQ elegido puede inspeccionar los árboles de expresión y convertirlos en un consulta real , aunque eso es un arte negro en sí mismo.
Esto realmente depende de ElementType
, Expression
y Provider
- pero en realidad rara vez necesita preocuparse por esto como usuario . Solo un implementador de LINQ necesita conocer los detalles sangrientos.
Re comentarios; No estoy muy seguro de lo que quiere a modo de ejemplo, pero considere LINQ-to-SQL; El objeto central aquí es un DataContext
, que representa nuestro contenedor de base de datos. Esto normalmente tiene una propiedad por tabla (por ejemplo,Customers
) y una tabla se implementa IQueryable<Customer>
. Pero no usamos mucho directamente; considerar:
using(var ctx = new MyDataContext()) {
var qry = from cust in ctx.Customers
where cust.Region == "North"
select new { cust.Id, cust.Name };
foreach(var row in qry) {
Console.WriteLine("{0}: {1}", row.Id, row.Name);
}
}
esto se convierte (por el compilador de C #):
var qry = ctx.Customers.Where(cust => cust.Region == "North")
.Select(cust => new { cust.Id, cust.Name });
que nuevamente es interpretado (por el compilador de C #) como:
var qry = Queryable.Select(
Queryable.Where(
ctx.Customers,
cust => cust.Region == "North"),
cust => new { cust.Id, cust.Name });
Es importante destacar que los métodos estáticos en Queryable
árboles de expresión de toma, que, en lugar de IL normal, se compilan en un modelo de objetos. Por ejemplo, solo mirando el "Dónde", esto nos da algo comparable a:
var cust = Expression.Parameter(typeof(Customer), "cust");
var lambda = Expression.Lambda<Func<Customer,bool>>(
Expression.Equal(
Expression.Property(cust, "Region"),
Expression.Constant("North")
), cust);
... Queryable.Where(ctx.Customers, lambda) ...
¿El compilador no hizo mucho por nosotros? Este modelo de objetos se puede desgarrar, inspeccionar para ver qué significa y volver a armar mediante el generador TSQL, dando algo como:
SELECT c.Id, c.Name
FROM [dbo].[Customer] c
WHERE c.Region = 'North'
(la cadena podría terminar como un parámetro; no recuerdo)
Nada de esto sería posible si acabáramos de usar un delegado. Y este es el punto de Queryable
/ IQueryable<T>
: proporciona el punto de entrada para usar árboles de expresión.
Todo esto es muy complejo, por lo que es un buen trabajo que el compilador lo haga agradable y fácil para nosotros.
Para obtener más información, consulte " C # en profundidad " o " LINQ en acción ", que proporcionan cobertura de estos temas.
Aunque Reed Copsey y Marc Gravell ya describieron sobre IQueryable
(y también IEnumerable
) lo suficiente, quiero agregar un poco más aquí al proporcionar un pequeño ejemplo IQueryable
y IEnumerable
tantos usuarios lo pidieron
Ejemplo : he creado dos tablas en la base de datos
CREATE TABLE [dbo].[Employee]([PersonId] [int] NOT NULL PRIMARY KEY,[Gender] [nchar](1) NOT NULL)
CREATE TABLE [dbo].[Person]([PersonId] [int] NOT NULL PRIMARY KEY,[FirstName] [nvarchar](50) NOT NULL,[LastName] [nvarchar](50) NOT NULL)
La clave primaria ( PersonId
) de la tabla Employee
también es una clave forgein ( personid
) de la tablaPerson
A continuación, agregué el modelo de entidad ado.net en mi aplicación y creo la clase de servicio a continuación en ese
public class SomeServiceClass
{
public IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable(IEnumerable<int> employeesToCollect)
{
DemoIQueryableEntities db = new DemoIQueryableEntities();
var allDetails = from Employee e in db.Employees
join Person p in db.People on e.PersonId equals p.PersonId
where employeesToCollect.Contains(e.PersonId)
select e;
return allDetails;
}
public IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable(IEnumerable<int> employeesToCollect)
{
DemoIQueryableEntities db = new DemoIQueryableEntities();
var allDetails = from Employee e in db.Employees
join Person p in db.People on e.PersonId equals p.PersonId
where employeesToCollect.Contains(e.PersonId)
select e;
return allDetails;
}
}
Contienen el mismo linq. Invocó program.cs
como se define a continuación
class Program
{
static void Main(string[] args)
{
SomeServiceClass s= new SomeServiceClass();
var employeesToCollect= new []{0,1,2,3};
//IQueryable execution part
var IQueryableList = s.GetEmployeeAndPersonDetailIQueryable(employeesToCollect).Where(i => i.Gender=="M");
foreach (var emp in IQueryableList)
{
System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
}
System.Console.WriteLine("IQueryable contain {0} row in result set", IQueryableList.Count());
//IEnumerable execution part
var IEnumerableList = s.GetEmployeeAndPersonDetailIEnumerable(employeesToCollect).Where(i => i.Gender == "M");
foreach (var emp in IEnumerableList)
{
System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
}
System.Console.WriteLine("IEnumerable contain {0} row in result set", IEnumerableList.Count());
Console.ReadKey();
}
}
La salida es la misma para ambos obviamente
ID:1, EName:Ken,Gender:M
ID:3, EName:Roberto,Gender:M
IQueryable contain 2 row in result set
ID:1, EName:Ken,Gender:M
ID:3, EName:Roberto,Gender:M
IEnumerable contain 2 row in result set
Entonces, la pregunta es ¿cuál / dónde está la diferencia? No parece tener ninguna diferencia ¿verdad? ¡¡De Verdad!!
Echemos un vistazo a las consultas sql generadas y ejecutadas por la entidad framwork 5 durante este período
Parte de ejecución IQueryable
--IQueryableQuery1
SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])
--IQueryableQuery2
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[Employee] AS [Extent1]
WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])
) AS [GroupBy1]
I Parte de ejecución numerable
--IEnumerableQuery1
SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)
--IEnumerableQuery2
SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)
Script común para ambas partes de ejecución
/* these two query will execute for both IQueryable or IEnumerable to get details from Person table
Ignore these two queries here because it has nothing to do with IQueryable vs IEnumerable
--ICommonQuery1
exec sp_executesql N'SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
--ICommonQuery2
exec sp_executesql N'SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=3
*/
Entonces tienes algunas preguntas ahora, déjame adivinarlas y tratar de responderlas
¿Por qué se generan diferentes scripts para el mismo resultado?
Veamos algunos puntos aquí,
todas las consultas tienen una parte común
WHERE [Extent1].[PersonId] IN (0,1,2,3)
¿por qué? Porque tanto function IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
como
IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
of SomeServiceClass
contienen una línea común en consultas linq
where employeesToCollect.Contains(e.PersonId)
Entonces, ¿por qué AND (N'M' = [Extent1].[Gender])
falta la
parte en la IEnumerable
parte de ejecución, mientras que en ambas llamadas a funciones usamos Where(i => i.Gender == "M") in
program.cs`
Ahora estamos en el punto donde la diferencia se interpuso entre
IQueryable
yIEnumerable
¿Qué entidad hace el marco cuando un IQueryable
se llama a método, toma la instrucción linq escrita dentro del método e intenta averiguar si se definen más expresiones linq en el conjunto de resultados, luego reúne todas las consultas linq definidas hasta que el resultado necesita buscar y construir sql más apropiado consulta a ejecutar.
Proporciona muchos beneficios como,
como aquí en el ejemplo, el servidor sql regresó a la aplicación solo dos filas después de la ejecución IQueryable` pero devolvió TRES filas para la consulta IEnumerable ¿por qué?
En el caso del IEnumerable
método, el marco de la entidad tomó la declaración linq escrita dentro del método y construye la consulta sql cuando el resultado necesita ser recuperado. no incluye la parte rest linq para construir la consulta sql. Al igual que aquí, no se realiza ningún filtrado en el servidor SQL en la columna gender
.
Pero las salidas son las mismas? Porque 'IEnumerable filtra el resultado aún más en el nivel de aplicación después de recuperar el resultado del servidor SQL
Entonces, ¿qué debería elegir alguien? Personalmente, prefiero definir el resultado de la función IQueryable<T>
porque , debido a que tiene muchos beneficios IEnumerable
, podría unir dos o más funciones IQueryable, que generan un script más específico para el servidor SQL.
Aquí, por ejemplo, puede ver y IQueryable Query(IQueryableQuery2)
genera un script más específico que el IEnumerable query(IEnumerableQuery2)
que es mucho más aceptable en mi punto de vista.
Permite realizar más consultas más adelante. Si esto fuera más allá de un límite de servicio, entonces, el usuario de este objeto IQueryable podría hacer más con él.
Por ejemplo, si estaba usando carga diferida con nhibernate, esto podría provocar que el gráfico se cargue cuando sea necesario.