在Linq中使用IQueryable
在LINQ的上下文中使用IQueryable
什么用?
它用于开发扩展方法还是用于其他目的?
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
。
作为开发人员,您的POV看起来是一样的。 但是,从性能的角度来看,您只能在网络上返回2条记录,而不是2万条....
实质上,它的工作与IEnumerable<T>
非常相似 - 代表一个可查询的数据源 - 区别在于各种LINQ方法(在Queryable
)可以更具体,使用Expression
树而不是委托来构建查询(这是Enumerable
使用什么)。
表达式树可以由您选择的LINQ提供者检查并转化为实际的查询 - 尽管这本身就是一种黑色艺术。
这实际上ElementType
, Expression
和Provider
- 但实际上你很少需要关心这个作为用户 。 只有LINQ实现者需要知道血腥的细节。
重新评论; 我不太清楚你想要什么,但考虑LINQ到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 in Action”,这两个主题都涵盖了这些主题。
虽然Reed Copsey和Marc Gravell已经描述了IQueryable
(也包括IEnumerable
),但是我想通过在IQueryable
和IEnumerable
上提供一个小例子来添加更多内容,因为很多用户都需要它
例如 :我在数据库中创建了两个表
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
所以问题是差异在哪里? 它似乎没有任何区别? 真!!
让我们看看在这段时间内由实体框架5生成和执行的sql查询
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)
为什么? 因为函数IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
和IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
SomeServiceClass
在linq查询中包含一条公共行
where employeesToCollect.Contains(e.PersonId)
为什么在IEnumerable
执行部分中缺少AND (N'M' = [Extent1].[Gender])
部分,而在函数调用中我们Where(i => i.Gender == "M") in
程序中使用Where(i => i.Gender == "M") in
.cs`
现在我们处于IQueryable
和IEnumerable
之间的区别了
当一个IQueryable
方法调用时,实体框架会执行什么操作,它会将LINQ语句写入方法内部,并尝试查找是否在结果集上定义了更多的linq表达式,然后收集定义的所有linq查询,直到结果需要获取并构造更合适的SQL查询来执行。
它提供了很多好处,
像这里在示例sql服务器返回到应用程序后IQueryable执行只有两行,但返回三行IEnumerable查询为什么?
在IEnumerable
方法的情况下,实体框架将结果写入该方法内部的linq语句并在结果需要获取时构造sql查询。 它不包含rest linq部分来构造sql查询。 像这里一样,没有在sql server中对列gender
进行过滤。
但输出是相同的? 因为'IEnumerable在从sql server检索结果后在应用程序级别进一步筛选结果
所以,有人应该选择什么? 我个人更喜欢将函数结果定义为IQueryable<T>
因为它对IEnumerable
有很多好处,您可以加入两个或更多的IQueryable函数,这些函数会为SQL Server生成更具体的脚本。
在示例中,您可以看到IQueryable Query(IQueryableQuery2)
生成比IEnumerable query(IEnumerableQuery2)
更具体的脚本,这在我看来更加可接受。