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

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


从本质上讲,它的工作非常类似于IEnumerable<T> -表示可查询数据源-不同之处在于各种LINQ方法(在可查询上)可以更具体,使用表达式树而不是委托(Enumerable使用的是委托)构建查询。

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

这实际上取决于ElementType、Expression和Provider——但实际上,作为用户,您很少需要关心这些。只有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 });

重要的是,可查询的静态方法采用表达式树,而不是常规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万条....


虽然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

所以问题是,区别在哪里?似乎没有 有区别吗?真的! !

让我们看一下由实体生成和执行的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
*/

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

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

我们来找一些点,

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

在[Extent1]。[PersonId] IN (0,1,2,3)

为什么?因为两个函数IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable和 someeserviceclass的IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable包含linq查询中的一个公共行

employeesToCollect.Contains (e.PersonId)

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

现在我们到了IQueryable和 IEnumerable

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

它有很多好处,比如,

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

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

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

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

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

在这里的例子中,你可以看到一个IQueryable查询(IQueryableQuery2)生成一个比IEnumerable查询(IEnumerableQuery2)更具体的脚本,这在我的观点是更可接受的。