将 IQueryable 与 Linq 一起使用

Posted

技术标签:

【中文标题】将 IQueryable 与 Linq 一起使用【英文标题】:Using IQueryable with Linq 【发布时间】:2010-12-07 10:02:56 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

Marc Gravell's answer 非常完整,但我想我也应该从用户的角度添加一些内容......


从用户的角度来看,主要区别在于,当您使用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&lt;T&gt; 提供程序,您可以:

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

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

从您作为开发人员的观点来看,这看起来是一样的。但是,从性能的角度来看,您可能只能通过网络返回 2 条记录,而不是 20,000 条......

【讨论】:

“GetQueryableProducts();”的定义在哪里? @***User 它旨在成为任何返回 IQueryable&lt;Product&gt; 的方法 - 特定于您的 ORM 或存储库等。 正确。但是您在此函数调用之后提到了 where 子句。所以系统仍然不知道过滤器。我的意思是它仍然获取产品的所有记录。对吗? @***User 否 - 这就是 IQueryable 的美妙之处 - 它可以设置为评估 当你得到结果时 - 这意味着 Where 子句,事后使用, 仍然会被翻译成在服务器上运行的 SQL 语句,并且只拉取所需的元素... @Testing 你实际上还没有去数据库。在您真正枚举结果之前(即:使用foreach,或调用ToList()),您实际上并没有命中数据库。【参考方案2】:

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

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

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


Re cmets;例如,我不太确定您想要什么,但请考虑 LINQ-to-SQL;这里的中心对象是DataContext,它代表我们的数据库包装器。这通常具有每个表的属性(例如,Customers),并且表实现了IQueryable&lt;Customer&gt;。但是我们并没有直接使用那么多;考虑:

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'

(字符串可能最终作为参数;我不记得了)

如果我们只使用委托,这一切都不可能实现。而this就是Queryable/IQueryable&lt;T&gt;的点:它提供了使用表达式树的入口。

所有这一切都非常复杂,所以编译器让我们变得轻松而轻松是一件好事。

有关详细信息,请查看“C# in Depth”或“LINQ in Action”,它们都提供了这些主题的报道。

【讨论】:

如果你不介意可以用简单易懂的例子更新我(如果你有时间的话)。 你能解释一下“GetQueryableProducts()的定义在哪里吗? “?”在 Reed Copsey 先生的回复中 享受将表达式翻译成查询的方式是“本身就是黑艺术”......这是很多道理 如果你使用了委托,为什么这一切都不可能? @Backwards_Dave 因为委托(本质上)指向 IL,而 IL 的表达能力不足,因此尝试充分解构意图以构建 SQL 是合理的。 IL 还允许 too many 的东西 - 即大多数可以在 IL 中表达的东西不能用有限的语法来表达,它可以合理地转化为 SQL 之类的东西 【参考方案3】:

虽然Reed Copsey 和Marc 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

所以问题是什么/哪里有区别?它似乎没有 有什么区别吗?真的!!

让我们看看实体生成和执行的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)

为什么?因为这两个功能 IQueryable&lt;Employee&gt; GetEmployeeAndPersonDetailIQueryableIEnumerable&lt;Employee&gt; GetEmployeeAndPersonDetailIEnumerable of SomeServiceClass 在 linq 查询中包含一个公共行

where employeesToCollect.Contains(e.PersonId)

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

现在我们正处于IQueryableIEnumerable

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

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

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

像这里示例中的 sql server 在 IQueryable 执行后只返回了两行,但是为 IEnumerable 查询返回了 行,为什么?

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

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

那么,人们应该选择什么? 我个人更喜欢将函数结果定义为IQueryable&lt;T&gt;,因为它比IEnumerable 有很多好处,例如,您可以加入两个或多个 IQueryable 函数,从而为 sql server 生成更具体的脚本。

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

【讨论】:

【参考方案4】:

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

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

【讨论】:

以上是关于将 IQueryable 与 Linq 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Breeze IQueryable 与 CORS 一起使用?

如何解决 - 无法使用Automapper将Generic.List强制转换为Linq.IQueryable?

如何将 Kendo UI Grid 与 ToDataSourceResult()、IQueryable<T>、ViewModel 和 AutoMapper 一起使用?

无法将类型“System.Linq.IQueryable<int>”隐式转换为“int?”

无法将匿名类型从“System.Linq.IQueryable”转换为“System.Data.Entity.Core.Objects.ObjectQuery”

我应该使用 Linq 和 IQueryable<T> 来实现查询对象模式