【问题标题】:How can I run a paged query in EF Core if Skip and Take are always evaluated locally?如果始终在本地评估 Skip 和 Take,我如何在 EF Core 中运行分页查询?
【发布时间】:2020-01-17 10:42:10
【问题描述】:

我有一个 ASP.NET Core Web API,它使用 Entity Framework Core(版本 2.0.2)返回一个名为 PhotoAlbum 的数据模型的分页列表。为此,它会建立一个IQueryable<PhotoAlbum>,如下所示:

    var query = _context.PhotoAlbums
        .Include(album => album.SpotlightPhotoView)
        .ApplySecurity(user)
        .ApplyFilter(filter)
        .Sort(sortInfo);

其中ApplySecurityApplyFilterSort 是我自己的扩展,它们分别应用了两个Where 过滤器和一个OrderBy 过滤器。最后,代码使用SkipTake 返回匹配数据的特定子集。

我很想在我的日志中看到以下警告:

无法翻译 LINQ 表达式“Take(__p_2)”,将在本地计算。

无法翻译 LINQ 表达式“Skip(__p_1)”,将在本地计算。

无法翻译 LINQ 表达式“where [album].Featured”,将在本地进行评估。

无法翻译 LINQ 表达式“where (([album].Complete OrElse False) OrElse False)”,将在本地进行计算。

阅读了这些警告后,我现在知道 EF Core 中的某些 Linq 方法无法转换为 SQL。其中包括聚合函数,如 SumCount 以及 SkipTake

所以我的第一个问题是:如果您想实现分页查询,推荐的解决方案是什么?存储过程是选项吗?

我的第二个问题是:为什么我会在 Where 子句周围出现警告?

为了详细说明第二个问题,应用[album].Featured 过滤器的代码如下所示...

    query = query.Where(album => album.Featured);

应用[album].Complete 过滤器的代码如下所示...

    query = query
        .Where(album =>
            album.Complete || filter.IncludeIncomplete
            && album.Published || filter.IncludeUnpublished);

...其中filter 是一个简单模型,其中包含一组定义如何过滤的布尔属性。

这是根据我的日志执行的实际 SQL(为了便于阅读,从 SELECT 中删除了几列):

SELECT [album].[AlbumID], [album].[AlbumDate], [album.SpotlightPhotoView].[PhotoViewID]
FROM [PhotoAlbum] AS [album]
LEFT JOIN [PhotoView] AS [album.SpotlightPhotoView] ON [album].[SpotlightPhotoViewID] = [album.SpotlightPhotoView].[PhotoViewID]
WHERE ([album].[Complete] = 1) AND ([album].[Featured] = 1)
ORDER BY [album].[AlbumDate] DESC

尽管有警告,它似乎已经很好地应用了 FeaturedComplete 过滤器。

【问题讨论】:

  • 您使用的是哪个 EF Core 版本?早期的 EF Core 版本没有实现每个 LINQ 运算符并使用本地评估。 EF Core 3 禁用了此功能。您是否尝试过将Skip()Take() 用于简单 LINQ 查询?
  • ApplySecurityApplyFilterSort 是做什么的?那些过滤器是什么?如果其中任何一个使用无法转换为 SQL 的表达式,则必须在本地评估整个查询。关于where [album].Featured 的警告也很奇怪——这是本地计算的属性吗?无法在服务器上进行评估,因为服务器不知道Featured 的公式是什么
  • 顺便说一句,These include aggregation functions like Sum and Count as well as Skip and Take. 不适用于聚合,因为 EF Core 2.1 是最新的 LTS(长期支持)版本。您使用的是较早的、可能不受支持的版本吗?
  • 顺便说一句,我无法在 LinqPad 中重现 EF 2.2.6 或 EF Core 3 Preview 8 的任何问题。在这两种情况下,查询 Posts.OrderBy(p=>p.PostId).Skip(100).Take(100) 都会导致 ORDER BY [p].[PostId] OFFSET @__p_0 ROWS FETCH NEXT @__p_0 ROWS ONLY
  • 所有 LINQ 函数都可以转换为 SQL。这是他们的主要目的之一,其中包括SkipTake。将代码贴在ApplySecurityApplyFilterSort 后面。您可能在那里做了一些奇怪的事情导致了这个问题。

标签: c# linq entity-framework-core


【解决方案1】:

我怀疑自定义方法及其输入会生成无法转换为 SQL 的表达式。 where (([album].Complete OrElse False) OrElse False) 绝对不能 - SQL 中没有 OrElseOrElse 是一个 VB.NET 关键字。

where [album].Featured 是另一个可疑警告。所有版本都绝对支持按布尔属性过滤。 Featured 可能是没有正确配置的计算属性吗?

除此之外,还添加了 GROUP BY 和聚合函数 in EF Core 2.1,这是最新的 LTS(长期支持)版本。

Take and Skip 绝对适用于 EF Core 2.2.6 和 EF Core 3.0 Preview 8。在 LinqPad 6 中尝试此查询:

Posts.OrderBy(p=>p.PostId).Skip(100).Take(100)

生成此 SQL 查询:

SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title]
FROM [Posts] AS [p]
ORDER BY [p].[PostId]
OFFSET @__p_0 ROWS FETCH NEXT @__p_0 ROWS ONLY

【讨论】:

  • OrElse 在 LINQ 查询表达式中不是 VB 关键字,而是表示逻辑 OR 的表达式(C# ||,VB OrElse 运算符) - Expression.OrElse。所以它肯定被支持并翻译成 SQL OR。最有可能的客户端 eval 是由于 2.0.2 对所讨论的Where 条件的翻译中的缺陷/错误造成的。当然,Where 的客户端评估自动意味着下一个运算符的客户端评估,例如 SkipTake
猜你喜欢
  • 1970-01-01
  • 2017-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-26
相关资源
最近更新 更多