que - linq c# custom
Usando IQueryable con Linq (4)
Aunque Reed Copsey y Marc Gravell ya describieron suficientemente IEnumerable
(y también IEnumerable
), quiero agregar un poco más aquí al proporcionar un pequeño ejemplo en IEnumerable
e IQueryable
ya que muchos usuarios lo solicitaron.
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 principal ( PersonId
) de la tabla Employee
también es una clave personid
( personid
) de la tabla Person
A continuación, agregué el modelo de entidad ado.net en mi aplicación y creé la siguiente 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;
}
}
contienen la misma linq. Llamó 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();
}
}
La salida es igual 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 es la diferencia? No parece haber ninguna diferencia, ¿verdad? ¡¡De Verdad!!
Echemos un vistazo a las consultas de sql generadas y ejecutadas por la entidad framwork 5 durante este período
Pieza 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]
IEnumerable parte de ejecución
--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)
Guión 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
*/
Así que ahora tienes pocas preguntas, déjame adivinar esas e intenta responderlas.
¿Por qué se generan diferentes scripts para el mismo resultado?
Vamos a descubrir algunos puntos aquí,
Todas las consultas tienen una parte común.
WHERE [Extent1].[PersonId] IN (0,1,2,3)
¿por qué? Debido a que ambas funciones IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
e IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
de SomeServiceClass
contiene una línea común en las consultas de linq
where employeesToCollect.Contains(e.PersonId)
AND (N''M'' = [Extent1].[Gender])
¿por qué AND (N''M'' = [Extent1].[Gender])
la parte AND (N''M'' = [Extent1].[Gender])
parte de ejecución de IEnumerable
, mientras que en la llamada de ambas funciones utilizamos Where(i => i.Gender == "M") in
programa .cs`
Ahora estamos en el punto de diferencia entre
IEnumerable
yIQueryable
IEnumerable
La estructura de la entidad hace cuando se llama un método IQueryable, tomó la instrucción linq escrita dentro del método y trata de averiguar si hay más expresiones linq definidas en el conjunto de resultados, luego reúne todas las consultas linq definidas hasta que el resultado deba buscarse y construirse de manera más apropiada. Consulta SQL para ejecutar.
Proporciona un montón de beneficios como,
- solo aquellas filas rellenadas por el servidor sql que podrían ser válidas por toda la ejecución de la consulta linq
- Ayuda al rendimiento del servidor SQL al no seleccionar filas innecesarias
- costo de red conseguir reducir
como aquí en el ejemplo, el servidor sql devolvió a la aplicación solo dos filas después de la ejecución de IQueryable` pero devolvió TRES filas para la consulta IEnumerable ¿por qué?
En el caso del método IEnumerable
, el marco de la entidad tomó la instrucción linq escrita dentro del método y construye la consulta SQL cuando se necesita obtener el resultado. no incluye la parte de linq de descanso para construir la consulta de SQL. Como aquí, no se realiza ningún filtrado en el servidor SQL en la columna de gender
.
Pero las salidas son las mismas? Porque ''IEnumerable filtra aún más el resultado 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 como IQueryable<T>
porque hay muchos beneficios que tiene sobre IEnumerable
, como por IEnumerable
, puedes unir dos o más funciones de IQueryable, que generan un script más específico para el servidor SQL.
Aquí, en el ejemplo, puede ver que una IQueryable Query(IQueryableQuery2)
IEnumerable query(IEnumerableQuery2)
genera un script más específico que la IEnumerable query(IEnumerableQuery2)
que es mucho más aceptable en mi punto de vista.
¿Cuál es el uso de IQueryable
en el contexto de LINQ?
¿Se utiliza para desarrollar métodos de extensión o cualquier otro propósito?
En esencia, su trabajo es muy similar a IEnumerable<T>
- para representar un origen de datos consultable - la diferencia es que los diversos métodos LINQ (en Queryable
) pueden ser más específicos, para construir la consulta utilizando árboles de Expression
lugar de delegados (que es lo que utiliza Enumerable
).
El proveedor de LINQ elegido puede inspeccionar los árboles de expresiones y convertirlos en una consulta real , aunque eso es un arte negro en sí mismo.
Esto se debe realmente a ElementType
, Expression
y Provider
, pero en realidad rara vez debe 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 nuestra envoltura de base de datos. Normalmente, esto tiene una propiedad por tabla (por ejemplo, Customers
), y una tabla 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 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 objeto. 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 objeto se puede desgarrar, inspeccionar por su significado y volver a armarlo con 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 puedo recordar)
Nada de esto sería posible si hubiésemos utilizado un delegado. Y este es el punto de Queryable
/ 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 agradable y fácil para nosotros.
Para obtener más información, consulte " C # en profundidad " o " LINQ en acción ", los cuales brindan cobertura de estos temas.
Permite una mayor consulta 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 usando la carga diferida con nhibernate, esto podría resultar en la carga del gráfico cuando sea necesario.
La respuesta de Marc Gravell es muy completa, pero también pensé que agregaría algo sobre esto desde el punto de vista del usuario ...
La principal diferencia, desde la perspectiva del usuario, es que, cuando utiliza IQueryable<T>
(con un proveedor que admite las cosas correctamente), puede ahorrar muchos recursos.
Por ejemplo, si está trabajando 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 una 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 transfiere 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 proveedor IQueryable<T>
correcto, 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 será SELECT * FROM Products WHERE Cost >= 25
.
Desde tu POV como desarrollador, esto se ve igual. Sin embargo, desde un punto de vista de rendimiento, solo puede devolver 2 registros a través de la red en lugar de 20,000 ...