我在理解 IQueryable<T> 时遇到问题

Posted

技术标签:

【中文标题】我在理解 IQueryable<T> 时遇到问题【英文标题】:I'm having problems understanding IQueryable<T> 【发布时间】:2011-03-17 13:27:18 【问题描述】:

所以我想了解IQueryable&lt;T&gt;。我正在阅读的教程建议使用它,但不确定为什么。该代码仅使用 LINQ to SQL 返回一些值。我过去做过很多次,但没有使用IQueryable&lt;T&gt;

为什么将它与返回超过 1 个值的函数一起使用?

这是我的代码:

public IQueryable<Items> GetItems()
    
        return from item in db.Items
               where item.IsActive == true
               orderby item.ItemNumber
               select item;
    

【问题讨论】:

好问题。你为我节省了数小时的研究时间。 【参考方案1】:

IQueryable 将查询表示为表达式树,而不在服务器上对其进行评估。这使您可以在实际生成 SQL 之前指定进一步的处理。

在上述情况下,这意味着您可以对调用 GetItems() 的结果进行处理,并将原始查询和额外内容作为单个查询发送:

var recentItems = from item in GetItems()
                  where item.Timestamp > somedate
                  select item;

foreach (var item in recentItems)

    // Do something with an active recent item.

在我们尝试使用 foreach 循环中的结果之前,不会向服务器发送任何内容。此时,LINQ-to-SQL 提供程序会评估整个表达式,包括在 GetItems() 内部生成的位和之后指定的位,并发出一条 SQL 语句来选择所有活动的和最近的项目。

为了澄清技术性,IQueryable&lt;T&gt; 是一个IEnumerable&lt;T&gt;,当您尝试对其调用GetEnumerator() 方法时,它的提供程序会计算最终的 SQL。您可以通过调用它来显式执行此操作,也可以通过在 foreach 语句中使用它来隐式执行此操作。此外,ToArray() 之类的扩展方法将在内部执行其中之一,从而产生相同的效果。

【讨论】:

IEnumerable 也是如此 - 它只会被评估 foreach.ToArray() 被调用。那么为什么 IQueryable 与 IEnumerable 不同呢? 一个 IQueryable is 一个 IEnumerable,它们都可以提供一个后期绑定的集合。但是,L2S 中 IQuerable 的意义在于延迟 SQL 执行。使用 IQueryable,您可以继续添加查询元素,例如 .Where() 条件,直到您最终开始枚举为止。此时,IQueryable 最终执行 SQL 查询并填充一个集合供您枚举。如果您确定您已完成查询并且调用您的方法不需要添加更多条件,那么您可能决定只调用 .ToList() 并在不返回 IQuerable 的情况下完成。 IQueryable&lt;T&gt; 实现了IEnumerable&lt;T&gt; 接口,因此IQueryable 可以用作IEnumerable。最大的区别在于 IQueryable 是操作的定义,而不是操作(编译代码)本身。这允许 LINQ to SQL 提供程序对其进行分析并将其转换为 SQL。这对于普通的编译代码来说(几乎)是不可能的。【参考方案2】:

IQueryable(在枚举之前)不是它们本身的结果,而是用于返回这些结果的逻辑。

使用 LINQ2SQL 示例...假设您返回一个 IQueryable 客户端。 在幕后,它不会是客户端列表(直到你 ToList() 它),但实际上是一个 SQL 查询,如下所示:

SELECT * FROM [Clients]

现在这很方便,因为我们还没有访问数据库!因此,假设当 IQueriable 回来时,我们希望将其细化为称为“Bob”的客户,我们可以这样做:

var clients = GetClients().Where(c => c.Name == "Bob");

现在 IQueriable 看起来像这样:

SELECT * FROM [Clients] WHERE Name = 'Bob'.

现在,当我执行 clients.ToList() 时,该查询将运行,数据库被命中,并且我有一个名为 bob 的客户端列表,而不必选择所有客户端然后在内存中搜索它们,或者执行 2 个单独的数据库命中。

举个例子,当你的数据上下文超出范围时(例如:在 using 语句中运行你的选择),你会在后面尝试获取子元素。这就是加载选项在 LINQ2SQL 中派上用场的地方。

希望有帮助

【讨论】:

IEnumerable 也是如此 - 它只被评估 .ToList() 被调用。那么为什么 IQueryable 与 IEnumerable 不同呢? 在枚举 IQueryable 之前,您的数据仍然是一个表达式树...在 LINQ2SQL 的示例中,您不能将 SELECT * FROM Clients 作为 IEnumerable,因为它是一个表达式并且不包含可枚举的值,直到它自己运行的查询。虽然它仍然是一个未枚举的 IQueryable,但您可以添加增长表达式树的子句。

以上是关于我在理解 IQueryable<T> 时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

如何检查 IQueryable<T>.Element 类型是不是为接口

Enumerable.Empty<T>() 等效于 IQueryable

IQueryable 和 IQueryable<T> 是异步的吗?

NSubstitute DbSet / IQueryable<T>

FakeItEasy DbSet / IQueryable<T> - 实体框架 6

返回 IEnumerable<T> 与 IQueryable<T>