在Linq上使用IQueryable


256

什么是使用IQueryable在LINQ的背景下?

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

Answers:


507

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条记录,而不是20,000...。


9
“ GetQueryableProducts();”的定义在哪里?
Pankaj

12
@StackOverflowUser它的目的是任何方法返回一个IQueryable<Product>-将是具体到你的ORM或仓库等
里德•科普塞

对。但是您在此函数调用之后提到了where子句。因此系统仍然不知道过滤器。我的意思是,它仍然会提取产品的所有记录。对?
Pankaj

@StackOverflowUser否-这就是IQueryable <T>的优点-它可以设置为在获取结果时进行评估-这意味着事后使用的Where子句仍将转换为在服务器上运行的SQL语句,并且仅将所需的元件拉过导线...
Reed Copsey

40
@Testing您实际上仍然不会去数据库。在您实际枚举结果之前(即使用foreach或调用ToList()),您实际上并没有访问数据库。
里德·科普西

188

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

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

这的确取决于ElementTypeExpression并且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 });

重要的是,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#深度 ”或“ LINQ in Action ”,这两种方法都涵盖了这些主题。


2
如果您不介意,可以举一个简单易懂的示例(如果有时间的话)来告诉我。
user190560

您能解释一下“ GetQueryableProducts()的定义在哪里吗?“?” 在Reed Copsey先生的回覆中
Pankaj

享受将表达式转换为查询的路线是“本身就是
妖术

如果您使用了委托,为什么都不可能呢?
David Klempfner '18

1
@Backwards_Dave,因为委托(本质上)指向IL,并且IL的表达能力不足,无法合理地试图充分构筑构建SQL的意图。IL还允许太多东西-即,可以用IL表示的大多数东西都不能用有限的语法来表达,因为它有可能变成SQL之类的东西
Marc Gravell

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查询

可查询执行部分

--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> GetEmployeeAndPersonDetailIQueryableIEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerableSomeServiceClass包含linq查询中的一条公共行

where employeesToCollect.Contains(e.PersonId)

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

现在我们在点差之间来了IQueryableIEnumerable

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

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

  • 只有那些由sql server填充的行在整个linq查询执行中可能是有效的
  • 通过不选择不必要的行来帮助SQL Server性能
  • 网络成本降低

喜欢这里例如在SQL Server的IQueryable execution`后只有两排返回到应用程序,但返回的排为IEnumerable的查询,为什么?

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

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

所以,有人应该选择什么?我个人更喜欢定义函数结果,IQueryable<T>因为它有很多好处,IEnumerable例如,您可以加入两个或多个IQueryable函数,这些函数将为SQL Server生成更特定的脚本。

在示例中,您可以看到一个IQueryable Query(IQueryableQuery2)生成的脚本比IEnumerable query(IEnumerableQuery2)我认为更可接受。


2

它允许进一步查询。如果这超出了服务范围,则将允许该IQueryable对象的用户执行更多操作。

例如,如果您将延迟加载与nhibernate一起使用,则可能会在需要时(如果需要)加载图形。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.