2009-10-16 20 views

Respuesta

421

Marc Gravell's answer es muy completo, pero pensé que me gustaría añadir algo acerca de esto desde el punto de vista del usuario, así ...


La principal diferencia, desde la perspectiva de un usuario, es que, cuando usa IQueryable<T> (con un proveedor que admite cosas correctamente), puede guardar muchos recursos.

Por ejemplo, si trabaja contra 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 IQueryable<T>. Supongamos, por ejemplo, que tiene una tabla de Productos y desea obtener todos los productos cuyo costo es> $ 25.

Si lo hace:

IEnumerable<Product> products = myORM.GetProducts(); 
var productsOver25 = products.Where(p => p.Cost >= 25.00); 

¿Qué ocurre aquí, es la carga de base de datos 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 tiene un SELECT * FROM Products, y le devuelve CADA producto.

Con el derecho de IQueryable<T> proveedor, por el contrario, que puede hacer:

IQueryable<Product> products = myORM.GetQueryableProducts(); 
var productsOver25 = products.Where(p => p.Cost >= 25.00); 

El código es el mismo, pero la diferencia aquí es que el SQL ejecutada será SELECT * FROM Products WHERE Cost >= 25.

Desde su POV como desarrollador, esto tiene el mismo aspecto. Sin embargo, desde el punto de vista de rendimiento, puede devolver sólo 2 registros a través de la red en lugar de 20.000 ....

+7

¿Dónde está la definición de "GetQueryableProducts();"? – Pankaj

+9

@StackOverflowUser Está destinado a ser cualquier método que devuelva un 'IQueryable ' - sería específico de su ORM o repositorio, etc. –

+0

right. pero mencionó la cláusula where después de llamar a esta función. Entonces el sistema aún no está enterado del filtro. Quiero decir que todavía busca todos los registros de productos. ¿derecho? – Pankaj

164

En esencia su trabajo es muy similar a IEnumerable<T> - para representar una fuente de datos consultable - la diferencia de que los diversos métodos de LINQ (en Queryable) pueden ser más específicos, para construir la consulta utilizando Expression árboles en lugar de los delegados (que es lo que usa Enumerable).

Los árboles de expresión pueden ser inspeccionados por su proveedor de LINQ elegido y convertidos en una consulta real, aunque se trata de un arte negro en sí mismo.

Esto es realmente hasta el ElementType, Expression y Provider - pero en realidad rara vez necesidad de preocuparse por esto como un usuario . Solo un implementador 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 implementa IQueryable<Customer>. Pero no usamos mucho directamente; Consideremos:

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 se interpreta de nuevo (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 toman árboles de expresión que, en lugar de IL normal, se compilan en un modelo de objetos. Por ejemplo - sólo mirar 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) ... 

No nos hizo el compilador de hacer mucho por nosotros? Este modelo de objetos puede ser destrozada, inspeccionados por lo que significa, y volver a poner juntos de nuevo por el generador de 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 me acuerdo)

Nada de esto sería posible si hubiéramos utilizado un delegado. Y este es el punto de Queryable/IQueryable<T>: proporciona el punto de entrada para usar árboles de expresiones.

Todo esto es muy complejo, por lo que es un buen trabajo que el compilador lo haga fácil y agradable.

Para obtener más información, consulte "C# in Depth" o "LINQ in Action", que brindan cobertura de estos temas.

+2

Si no te importa puedes actualizarme con un ejemplo simple y comprensible (si tienes tiempo). – user190560

+0

¿Puede explicar "Dónde está la definición de" GetQueryableProducts(); "¿?" en Mr Reed Copsey respuesta – Pankaj

+0

Disfruté la línea de traducción de expresiones a una consulta es "arte negro en sí mismo" ... mucha verdad a eso – afreeland

2

Permite consultas adicionales más adelante en la línea.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 utilizando la carga diferida con nhibernate, esto podría provocar que el gráfico se cargue cuando/si es necesario.

10

Aunque Reed Copsey y Marc Gravell ya se ha descrito sobre IQueryable(and also IEnumerable) suficiente, sin embargo, quiero añadir algo más aquí proporcionando un small example on IQueryable and IEnumerable como muchos usuarios les pide de ella

Ejemplo: he creado dos tipos de tabla de base de datos en

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) 

Primary key(PersonId) de mesa Employee es también forgein key(personid) de la tabla Person

A continuación añadí modelo de entidad ado.net en mi aplicación y crear a continuación clase de servicio 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; 
    } 
} 

que contiene misma LINQ.Se llama en 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(); 
    } 
} 

El output is same for both 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 

Así que la pregunta es ¿qué/dónde está la diferencia? No parece que tenga alguna diferencia, ¿verdad? ¡¡De Verdad!!

Vamos a echar un vistazo en las consultas SQL generadas y ejecutadas por la entidad framwork 5 durante dichos períodos

IQueryable execution part

--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] 

IEnumerable execution part

--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) 

Common script for both execution part

/* 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 
*/ 

Así que hay algunas preguntas ahora, déjame adivinar los e intentar responderlas

Permite encontrar algunos puntos aquí,

todas las consultas tienen una parte común

WHERE [Extent1].[PersonId] IN (0,1,2,3)

por qué? Debido a que tanto la función IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable y IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable de SomeServiceClass contiene uno common line in linq queries

where employeesToCollect.Contains(e.PersonId)

que de por qué AND (N'M' = [Extent1].[Gender]) parte no se encuentra en IEnumerable execution part, mientras tanto en la función de llamar utilizamos Where(i => i.Gender == "M") en program.cs

Ahora estamos en el punto donde la diferencia se produjo entre IQueryable y IEnumerable

Lo entity framwork hace cuando un método llamado IQueryable, que tooks linq statement written inside the method y tratar de averiguar si more linq/expression is defind on the resultset, se reúnen todas las consultas LINQ definidos hasta que el resultado tenga que traer y constructs more appropriate sql query to execute.

Se proporcionan una gran cantidad de beneficios como,

  • sólo las filas pobladas servidor SQL que podrían ser válidos por la ejecución de la consulta toda LINQ
  • ayuda al rendimiento del servidor SQL al no seleccionar filas innecesarias
  • coste de la red consigue reducir

como aquí en el ejemplo de servidor de SQL que devuelve a la aplicación sólo two rows after IQueryable execution pero returned THREE rows for IEnumerable query ¿por qué?

En el caso del método IEnumerable, entity framework tomó la sentencia linq escrita dentro del método y construye la consulta sql cuando el resultado debe buscarse. it does not include rest linq part to constructs the sql query. Al igual que aquí no se realiza ningún filtrado en el servidor sql en la columna gender.

¿Pero las salidas son las mismas? Debido a que 'IEnumerable filtra el resultado aún más en el nivel de la aplicación después de recuperar el resultado de sql server

SO, ¿qué debería elegir alguien? Personalmente prefiero definir el resultado de la función como IQueryable<T> porque hay muchos beneficios que tiene sobre 'IEnumerable' como, podría join two or more IQueryable function que generan secuencias de comandos más específicas para el servidor sql.

Aquí en el ejemplo se puede ver una IQueryable Query(IQueryableQuery2) Generar guión más específico que IEnumerable query(IEnumerableQuery2) que es mucho más aceptable en my point of view.

Cuestiones relacionadas