Entity Framework 6:Skip()和Take()不会生成SQL语句,而是在加载到内存后过滤结果集。或者我做错了什么?

7

我有以下的代码,应该获取一些书籍,并从该书籍(Book实体)中检索前2个标签Tag实体)。所以TagsBook实体的一个导航属性

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] */

这里是一个方案片段,如果需要可以查看

我还尝试将.AsQueryable()附加到firstBook.Tags属性和/或删除.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时遇到相同的问题?

是否有任何方法可以解决这个问题,或者是我设计查询方式的问题...?

感谢任何提示!


为什么要使用Skip、Take和SingleOrDefault?如果您不关心标签的数量,只需将Skip().Take().SingleOrDefault()替换为.FirstOrDefault()。 - Sebastian Brand
@SebastianBrand 说实话,我打算通过Skip(toBeSkipped).Take(pageSize)来实现分页。我会更新问题以使其更加清晰明了。 - AlexMelw
2个回答

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可以理解的内容。


谢谢!你说得对。我不得不对你的解决方案进行一些小的更改才能使其工作。当我运行你的代码(原样)时,我遇到了以下异常:“Unhandled Exception: System.NotSupportedException: Unable to create a constant value of type 'Repositories.Implementation.Book'. Only primitive types or enumeration types are supported in this context”。但是在将.Where(tag => tag.Books.Contains(firstBook))替换为.Where(tag => tag.Books.Any(book => book.Id == firstBook.Id))之后,使用* LINQ to SQL * 我获得了想要构造的SQL查询 - AlexMelw
1
@Andrey-WD 我的第二个想法是 .Where(tag => tag.Books.Select(book => book.Id).Contains(firstBook.Id)),但你的查询看起来也是正确的。很高兴你解决了它。 - user743382

5
正如@hvd指出的那样,我必须使用,而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 的解决方案的小改动是:用 替换 .Where(tag => tag.Books.Contains(firstBook))
使用 EF 可以理解的东西
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接