274

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

它是否用于开发扩展方法或任何其他目的?

4

4 回答 4

546

Marc Gravell 的回答非常完整,但我想我也会从用户的角度添加一些内容......


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

例如,如果您正在处理具有许多 ORM 系统的远程数据库,您可以选择以两种方式从表中获取数据,一种返回IEnumerable<T>,另一种返回IQueryable<T>. 例如,假设您有一个 Products 表,并且您想要获取成本大于 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 条记录,而不是 20,000 条......

于 2009-10-16T16:08:43.667 回答
194

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

您选择的 LINQ 提供程序可以检查表达式树并将其转换为实际查询 - 尽管这本身就是一种黑艺术。

这真的取决于ElementTypeExpression并且Provider- 但实际上,作为用户,您很少需要关心这一点。只有 LINQ实现者需要知道血淋淋的细节。


重新评论;例如,我不太确定您想要什么,但请考虑 LINQ-to-SQL;这里的中心对象是 a 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,它被编译为对象模型。例如 - 只看“哪里”,这给了我们类似的东西:

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# in Depth ”或“ LINQ in Action ”,它们都涵盖了这些主题。

于 2009-10-16T15:28:31.833 回答
17

尽管Reed 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)

表的主键(PersonId)也是表Employee的伪造键(personidPerson

接下来,我在我的应用程序中添加了 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)

为什么?因为 functionIQueryable<Employee> GetEmployeeAndPersonDetailIQueryableIEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerableofSomeServiceClass在 linq 查询中都包含一个公共行

where employeesToCollect.Contains(e.PersonId)

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

IQueryable现在我们正处于和 之间产生差异的地步IEnumerable

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

它提供了很多好处,例如,

  • 只有那些由 s​​ql server 填充的行可能对整个 linq 查询执行有效
  • 通过不选择不必要的行来提高 sql server 性能
  • 网络成本降低

就像这里在示例 sql server 在 IQueryable 执行后仅返回到应用程序的两行一样,但是为 IEnumerable 查询返回了三行,为什么

IEnumerable方法的情况下,实体框架将 linq 语句写入方法内部,并在需要获取结果时构造 sql 查询。它不包括构建 sql 查询的 rest linq 部分。像这里一样,在 sql server 的 column 上没有进行过滤gender

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

那么,有人应该选择什么?我个人更喜欢定义函数结果,IQueryable<T>因为它有很多好处IEnumerable,你可以加入两个或多个 IQueryable 函数,这些函数会为 sql server 生成更具体的脚本。

在示例中,您可以看到IQueryable Query(IQueryableQuery2)生成了一个更具体的脚本,IEnumerable query(IEnumerableQuery2)而不是在我看来更容易接受的脚本。

于 2016-02-25T14:00:44.587 回答
2

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

例如,如果您在 nhibernate 中使用延迟加载,这可能会导致在需要时/如果需要时加载图形。

于 2009-10-16T15:29:24.310 回答