使用IQueryable Linq

IQueryable在LINQ上下文中有什么用途?

它是用于开发扩展方法还是其他用途?

318656 次浏览

本质上,它的工作与IEnumerable<T>非常相似——表示一个可查询的数据源——区别在于各种LINQ方法(在Queryable上)可以更具体,使用Expression树而不是委托(这是Enumerable使用的)来构建查询。

表达式树可以由你选择的LINQ提供者检查,并转换为实际查询——尽管这本身就是一种黑魔法。

这实际上归结为ElementTypeExpressionProvider -但实际上你很少需要把它作为用户来关心。只有LINQ 实现者需要知道这些血腥的细节。


重新评价;举例来说,我不太确定你想要什么,但考虑一下LINQ-to-SQL;这里的中心对象是DataContext,它表示我们的数据库包装器。通常每个表都有一个属性(例如,Customers),一个表实现了IQueryable<Customer>。但我们不会直接使用那么多;考虑:

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

这就变成了(由c#编译器):

var qry = ctx.Customers.Where(cust => cust.Region == "North")
.Select(cust => new { cust.Id, cust.Name });

它再次被(由c#编译器)解释为:

var qry = Queryable.Select(
Queryable.Where(
ctx.Customers,
cust => cust.Region == "North"),
cust => new { cust.Id, cust.Name });

重要的是,Queryable上的静态方法采用表达式树,而不是常规IL,被编译为对象模型。例如,只看“Where”,这给了我们一些类似的东西:

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

编译器不是为我们做了很多吗?这个对象模型可以被分解,检查它的含义,然后由TSQL生成器重新组合起来——给出如下内容:

 SELECT c.Id, c.Name
FROM [dbo].[Customer] c
WHERE c.Region = 'North'

(字符串可能以形参形式结束;我不记得了)

如果我们只使用委托,这些都不可能实现。而Queryable / IQueryable<T>的点:它提供了使用表达式树的入口点。

所有这些都是非常复杂的,所以编译器让我们很容易就完成了。

有关更多信息,请查看“c#深度”或“LINQ的应用”,它们都提供了这些主题的覆盖。

它允许进一步查询。如果这超出了服务边界,那么IQueryable对象的用户将被允许使用它做更多的事情。

例如,如果你在nhibernate中使用惰性加载,这可能会导致图形在需要时被加载。

Marc Gravell的回答是是非常完整的,但我认为我应该从用户的角度添加一些东西,以及…


从用户的角度来看,主要的区别是,当你使用IQueryable<T>(与正确支持事物的提供者一起使用)时,你可以节省大量资源。

例如,如果您使用许多ORM系统对远程数据库进行操作,您可以选择以两种方式从表中获取数据,一种返回IEnumerable<T>,另一种返回IQueryable<T>。例如,您有一个产品表,您希望获得成本为>$25的所有产品。

如果你有:

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

这里发生的是,数据库加载所有的产品,并将它们通过网络传递给你的程序。然后,程序对数据进行筛选。本质上,数据库执行SELECT * FROM Products,并将每个产品返回给你。

另一方面,使用正确的IQueryable<T>提供程序,你可以这样做:

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

代码看起来是一样的,但不同之处在于执行的SQL将是SELECT * FROM Products WHERE Cost >= 25

从开发者的角度来看,这看起来是一样的。但是,从性能的角度来看,您可能只在网络上返回2条记录,而不是2万条....

虽然里德CopseyMarc Gravell已经对IQueryable(以及IEnumerable)进行了足够多的描述,但我想在这里补充一点,因为许多用户要求提供一个关于IQueryableIEnumerable的小例子

例子:我在数据库中创建了两个表

   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)

Employee的主键(PersonId)也是表Person的外键(personid)

接下来,我在我的应用程序中添加了ado.net实体模型,并在其上创建下面的服务类

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;
}
}

它们包含相同的linq。它调用下面定义的program.cs

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();
}
}

两者的输出显然是相同的

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
所以问题是区别在哪里?似乎没有 有区别吗?真的! !< / p > 让我们看看由实体生成和执行的sql查询 框架5在这段时间

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执行部分

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

两个执行部分的通用脚本

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

你现在有几个问题,让我猜一下,然后回答

为什么为相同的结果生成不同的脚本?

我们来找一些点,

所有查询都有一个共同的部分

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

< p >为什么?因为两个函数都是IQueryable<Employee> GetEmployeeAndPersonDetailIQueryableSomeServiceClassIEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable包含linq查询

中的一个公共行

where employeesToCollect.Contains(e.PersonId)

那为什么 AND (N'M' = [Extent1].[Gender])部分在IEnumerable执行部分中缺失,而在这两个函数调用中,我们使用了Where(i => i.Gender == "M") inprogram.cs的

现在我们处在IQueryableIEnumerable < / p >

IQueryable方法被调用时,实体框架所做的是,它使用方法内部编写的linq语句,并尝试找出结果集上是否定义了更多的linq表达式,然后它收集所有定义的linq查询,直到需要获取结果并构造更合适的sql查询来执行。

它有很多好处,比如,

  • 只有SQL server填充的那些行 整个linq查询执行
  • 通过不选择不必要的行来提高SQL服务器的性能
  • 网络成本降低

就像这里的例子,sql server在IQueryable执行后仅返回应用程序两行,但返回三个行用于IEnumerable查询为什么?

对于IEnumerable方法,实体框架采用方法内部编写的linq语句,在需要获取结果时构造sql查询。它不包括其余的linq部分来构造SQL查询。像这里一样,sql server中没有对列gender进行过滤。

但是输出是一样的吗?因为'IEnumerable从sql server中检索结果后,在应用程序级别进一步过滤结果

所以,人们应该选择什么? 我个人更喜欢将函数结果定义为IQueryable<T>,因为它比IEnumerable有很多好处,比如,你可以连接两个或多个IQueryable函数,这将为sql server生成更具体的脚本

在这个例子中,你可以看到IQueryable Query(IQueryableQuery2)生成的脚本比IEnumerable query(IEnumerableQuery2)更具体,这在我看来更容易接受。