在 Mongo 查询中混合本机查询和 LINQ

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

让我们看一下向标准 MongoCollection<T> 实例对象发出的以下查询:


 return _bufferCollection.Find(
        GetNextBlockQuery(lastTick, lastRevisionId))
    .OrderBy(d => d.LastUpdated)
    .ThenBy(d => d.RevisionIdNumeric);

GetNextBlockQuery 方法仅返回一个用 C# mongo 查询语法表示的 Query<T> 查询对象。在此查询中 ,使用标准 LINQ 语法简单地对 Find() 方法的结果进行了排序

你发现问题出在哪里了吗?

Find() 方法返回一个 MongoCursor<T> 类型的对象,它实现了 IEnumerable<T> 但没有实现 IQueryable<T>

如果使用 AsQueryable() 扩展方法通过 LINQ 查询 MongoCollection,则使用 OrderBy() 或 ThenBy() LINQ 扩展方法没有问题。在这种情况 下,Mongo C# 驱动程序中 IQueryable 的实现会将所有内容转换为标准的 mongo 查询语法, 然后它向服务器执行转换后的查询并将对象返回给调用者。

在前面的示例中,针对 MongoCursor 调用了 OrderBy() LINQ 运算符, 排序将在内存中完成。 问题是:OrderBy 方法将对 IEnumerable 对象进行操作并迭代所有对象以按正确的顺序返回它们。

如果针对标准 MongoCursor 使用 LINQ 运算符,它将在内存中运行,从而影响性能。

这会损害应用程序的性能:每次执行查询时, 整个结果集都会加载到内存中,然后进行排序。 为避免此问题,您不需要将本机 Mongo C# 查询与 LINQ 运算符混合使用。正确的查询如下:


 return _bufferCollection.Find(
        GetNextBlockQuery(lastTick, lastRevisionId))
    .OrderBy(d => d.LastUpdated)
    .ThenBy(d => d.RevisionIdNumeric);

这个新版本使用 Mongo C# Query 的 SetSortOrder() 方法,因此它将直接从 Mongo 服务器排序,并且对象将在标准的 for-each 枚举期间加载到内存中。 如果你想限制返回对象的数量,上面的问题真的很糟糕。 如果使用 Take(50) 方法只获取 50 个对象,实际上是将整个集合加载到内存中,然后返回前 50 个元素。这与直接在查询中要求 mongo 只返回 50 个元素是完全不同的。

最大的问题之一是,如果您在第一次查询时使用 LINQ 运算符 Ta​​ke() 限制记录数,那么您将进行客户端分页,从而导致性能损失显着。

作为一般规则,避免混合使用 LINQ 和 Mongo 查询类来向您的 Mongo 服务器发出查询,并且 比 LINQ 更喜欢本机查询语法,因为它会为您提供 Mongo 的全部功能 。相反,LINQ 查询将仅公开可能查询的一个子集,并注意 Select 运算符在内存中运行,而不是直接从服务器限制返回字段的数量。