Entity Framework 6:Skip() & Take() 不生成SQL,而是加载到内存后过滤结果集。还是我做错了什么?

Entity Framework 6: Skip() & Take() do not generate SQL, instead the result set is filtered after loading into memory. Or am I doing something wrong?

我有以下代码应该获取一些 book,并从那本书(Book 实体)中检索前 2 tags(Tag 实体) . 所以 TagsBook 实体的 navigation property

using (var context = new FakeEndavaBookLibraryEntities())
{
      Book firstBook = context.Set<Book>().Take(1).First();
      var firstTwoTags = firstBook.Tags.OrderBy(tag => tag.Id).Skip(0).Take(2).ToList();
}

期望 获得必须由 EF 生成的以下 SQL 查询。

SELECT TOP(2)
       [Extent2].[Id]      AS [Id],
       [Extent2].[Version] AS [Version],
       [Extent2].[Name]    AS [Name]
FROM   [Literature].[BookTagRelation] AS [Extent1]
       INNER JOIN [Literature].[Tag] AS [Extent2]
         ON [Extent1].[TagId] = [Extent2].[Id]
WHERE  [Extent1].[BookId] = 1 /* @EntityKeyValue1 - [BookId] */

相反,EF Profiler 向我显示 EF 正在生成 无限结果集(如 SELECT * FROM ...)

SELECT [Extent2].[Id]      AS [Id],
       [Extent2].[Version] AS [Version],
       [Extent2].[Name]    AS [Name]
FROM   [Literature].[BookTagRelation] AS [Extent1]
       INNER JOIN [Literature].[Tag] AS [Extent2]
         ON [Extent1].[TagId] = [Extent2].[Id]
WHERE  [Extent1].[BookId] = 1 /* @EntityKeyValue1 - [BookId] */

Here is a scheme fragment if you need it

我还尝试将附加.AsQueryable()firstBook.Tags属性and/or删除.Skip(0)方法作为如下所示,但这也没有帮助

      var firstTwoTags = firstBook.Tags.AsQueryable().OrderBy(tag => tag.Id).Skip(0).Take(2).ToList();

同样的不良行为:

SELECT [Extent2].[Id]      AS [Id],
       [Extent2].[Version] AS [Version],
       [Extent2].[Name]    AS [Name]
FROM   [Literature].[BookTagRelation] AS [Extent1]
       INNER JOIN [Literature].[Tag] AS [Extent2]
         ON [Extent1].[TagId] = [Extent2].[Id]
WHERE  [Extent1].[BookId] = 1 /* @EntityKeyValue1 - [BookId] */

您在使用 Entity Framework 6 时遇到过同样的问题吗?

是否有解决方法来解决这个问题,或者我以错误的方式设计了查询...?

感谢任何提示!

firstBook.Tags 是延迟加载的 IEnumerable<Tag>。在第一次访问时,所有标签都被加载,随后尝试将其变成 IQueryable<Tag> 无效,因为您没有从可以合理查询的内容开始。

相反,从已知的商品开始 IQueryable<Tag>。类似于

Tag firstTag = context.Set<Tag>()
    .Where(tag => tag.Books.Contains(firstBook))
    .OrderBy(tag => tag.Id).Skip(0).Take(1).SingleOrDefault();

应该可以。您可能需要稍作调整才能将过滤条件变成 EF 可以理解的内容。

正如@hvd 指出的那样,我必须使用 IQueryable<Tag>,而 firstBook.Tags 导航 属性 只是延迟加载 IEnumerable<Tag>。 所以这是我的问题的解决方案,基于@hvd的回答。

Tag firstTag = context.Set<Tag>() // or even context.Tags
    .Where(tag => tag.Books.Any(book => book.Id == firstBook.Id))
    .OrderBy(tag => tag.Id)
    .Skip(0).Take(1)
    .SingleOrDefault();

所以@hvd的解决方案的小改动是:替换 the

.Where(tag => tag.Books.Contains(firstBook))

Something that EF understands

1) .Where(tag => tag.Books.Any(book => book.Id == firstBook.Id)).

2) .Where(tag => tag.Books.Select(book => book.Id).Contains(firstBook.Id))

任何代码序列 (1) 或 (2) 都会生成以下 SQL 查询,这绝对是 不再是 一个 无界结果集.

SELECT [Project2].[Id]      AS [Id],
       [Project2].[Version] AS [Version],
       [Project2].[Name]    AS [Name]
FROM   (SELECT [Extent1].[Id]      AS [Id],
               [Extent1].[Version] AS [Version],
               [Extent1].[Name]    AS [Name]
        FROM   [Literature].[Tag] AS [Extent1]
        WHERE  EXISTS (SELECT 1 AS [C1]
                       FROM   [Literature].[BookTagRelation] AS [Extent2]
                       WHERE  ([Extent1].[Id] = [Extent2].[TagId])
                              AND ([Extent2].[BookId] = 1 /* @p__linq__0 */))) AS [Project2]
ORDER  BY [Project2].[Id] ASC
OFFSET 0 ROWS
FETCH NEXT 1 ROWS ONLY