在.NET Core中使用MongoDB明细教程(3):Skip, Sort, Limit, Projections
到目前为止,我们已经讨论了创建文档, 检索文档,现在让我们来研究一下文档排序,指定要跳过或限制返回的文档数量,以及如何进行投影。此篇文章中的实例代码摘录自原文,未像前几篇文章一样进行实际代码的验证。
作者:依乐祝
Limit
当我们查询一个文档时,我们有时不想返回所有符合过滤条件的文档,而只返回其中的一部分。这就是limit
方法的具体应用。对于MongoDB,可以通过调用Find
返回的IFindFluent
的limit
方法来限制文档的数量。因此,如果我查询数据库中年龄小于40岁的学生,我会得到以下信息:
S/N: 1 Id: 582489339798f091295b9094, FirstName: Gregor, LastName: Felix
S/N: 2 Id: 582489339798f091295b9095, FirstName: Machiko, LastName: Elkberg
S/N: 3 Id: 582489339798f091295b9096, FirstName: Julie, LastName: Sandal
S/N: 4 Id: 583da304f03a84d4d4f4678d, FirstName: Peter, LastName: Cyborg
为了让它把结果限制在最多两个学生,我调用了Limit()
方法,并传递值为2的参数:
int count = 1;
await collection.Find(x => x.Age < 40)
.Limit(2)
.ForEachAsync(
student =>
{
Console.WriteLine($"S/N: {count} \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}");
count++;
});
然后得到以下输出,它只返回两个文档:
S/N: 1, Id: 582489339798f091295b9094, FirstName: Gregor, LastName: Felix
S/N: 2, Id: 582489339798f091295b9095, FirstName: Machiko, LastName: Elkberg
Skip
如果我们想告诉数据库要跳过多少文档,我们使用fluent
接口中的skip
方法。因此,它类似于我们之前使用的代码,但是告诉数据库返回年龄小于40的所有代码,并跳过第一个。
int count = 1;
await collection.Find(x => x.Age < 40)
.Skip(1)
.ForEachAsync(
student =>
{
Console.WriteLine($"S/N: {count} \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}");
count++;
});
S/N: 1, Id: 582489339798f091295b9095, FirstName: Machiko, LastName: Elkberg
S/N: 2, Id: 582489339798f091295b9096, FirstName: Julie, LastName: Sandal
S/N: 3, Id: 583da304f03a84d4d4f4678d, FirstName: Peter, LastName: Cyborg
你会注意到Gregor Felix
被跳过了。使用skip
和sort
,我们可以将分页添加到应用程序中。
假设我们要检索集合中的每个学生,一个页面上最多显示两个学生。我们可以通过如下过程实现:
跟踪当前页面和要检索的最大文档数。
确定总页数。
然后检索文档,同时相应地应用
skip
和limit
。
我们可以使用以下代码来完成此操作,并将每个页面的结果打印到控制台:
var client = new MongoClient();
var db = client.GetDatabase("schoool");
var collection = db.GetCollection<Student>("students");
int currentPage = 1, pageSize = 2;
double totalDocuments = await collection.CountAsync(FilterDefinition<Student>.Empty);
var totalPages = Math.Ceiling(totalDocuments / pageSize);
for (int i = 1; i <= totalPages; i++)
{
Console.WriteLine($"Page {currentPage}");
Console.WriteLine();
int count = 1;
await collection.Find(FilterDefinition<Student>.Empty)
.Skip((currentPage - 1) * pageSize)
.Limit(pageSize)
.ForEachAsync(
student =>
{
Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}");
count++;
});
Console.WriteLine();
currentPage++;
}
我们在控制台窗口中得到以下结果:
Page 1
S/N: 1, Id: 58469c732adc9f5370e50c9c, FirstName: Gregor, LastName: Felix
S/N: 2, Id: 58469c732adc9f5370e50c9d, FirstName: Machiko, LastName: Elkberg
Page 2
S/N: 1, Id: 58469c732adc9f5370e50c9e, FirstName: Julie, LastName: Sandal
S/N: 2, Id: 58469c732adc9f5370e50c9f, FirstName: Peter, LastName: Cyborg
Page 3
S/N: 1, Id: 58469c732adc9f5370e50ca0, FirstName: James, LastName: Cyborg
这样,我们得到三个页面,因为我们总共有五个记录,每页最多检索两个文档。
Sort
fluent
接口的Sort
方法采用SortDefinition
作为参数,它可以从string
或BsonDocument
隐式转换,就像FilterDefinition
一样。因此,如果我们想使用字符串作为排序定义,按姓氏升序排序,那么它将是:
await collection.Find(FilterDefinition<Student>.Empty)
.Skip((currentPage - 1) * pageSize)
.Limit(pageSize)
.Sort("{LastName: 1}")
.ForEachAsync(
student =>
{
Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
count++;
});
在字符串中,我们有{LastName:1}
,其中1告诉它升序排序,而-1告诉它按降序排序。如果我们使用前面更新的代码运行应用程序,它会在第一页返回James和Peter作为结果,如下所示:
Page 1
S/N: 1, Id: 58469c732adc9f5370e50ca0, FirstName: James, LastName: Cyborg, Age: 39
S/N: 2, Id: 58469c732adc9f5370e50c9f, FirstName: Peter, LastName: Cyborg, Age: 39
Page 2
S/N: 1, Id: 58469c732adc9f5370e50c9d, FirstName: Machiko, LastName: Elkberg, Age: 23
S/N: 2, Id: 58469c732adc9f5370e50c9c, FirstName: Gregor, LastName: Felix, Age: 23
Page 3
S/N: 1, Id: 58469c732adc9f5370e50c9e, FirstName: Julie, LastName: Sandal, Age: 25
如果我们希望使用BsonDocument
将姓氏按降序排列,则这将是:
await collection.Find(FilterDefinition<Student>.Empty)
.Skip((currentPage - 1) * pageSize)
.Limit(pageSize)
.Sort(new BsonDocument("LastName", -1))
.ForEachAsync(
student =>
{
Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
count++;
});
给出了与之前结果相反的结果:
Page 1
S/N: 1, Id: 58469c732adc9f5370e50c9e, FirstName: Julie, LastName: Sandal, Age: 25
S/N: 2, Id: 58469c732adc9f5370e50c9c, FirstName: Gregor, LastName: Felix, Age: 23
Page 2
S/N: 1, Id: 58469c732adc9f5370e50c9d, FirstName: Machiko, LastName: Elkberg, Age: 23
S/N: 2, Id: 58469c732adc9f5370e50ca0, FirstName: James, LastName: Cyborg, Age: 39
Page 3
S/N: 1, Id: 58469c732adc9f5370e50c9f, FirstName: Peter, LastName: Cyborg, Age: 39
我们也可以使用SortDefinitionBuilder
。因此,我们可以使用构建器帮助方法更新代码以创建一个排序定义,如下所示:
await collection.Find(FilterDefinition<Student>.Empty)
.Skip((currentPage - 1) * pageSize)
.Limit(pageSize)
.Sort(Builders<Student>.Sort.Descending("LastName"))
.ForEachAsync(
student =>
{
Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
count++;
});
我们仍然可以得到相同的结果,我们还可以组合不同字段上的升序和降序列表:
await collection.Find(FilterDefinition<Student>.Empty)
.Skip((currentPage - 1) * pageSize)
.Limit(pageSize)
.Sort(Builders<Student>.Sort.Descending("LastName").Ascending("FirstName"))
.ForEachAsync(
student =>
{
Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
count++;
});
或使用强类型对象时,使用表达式树:
await collection.Find(FilterDefinition<Student>.Empty)
.Skip((currentPage - 1) * pageSize)
.Limit(pageSize)
.Sort(Builders<Student>.Sort.Descending(x => x.LastName).Ascending(x => x.FirstName))
.ForEachAsync(
student =>
{
Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
count++;
});
我们还可以使用表达式树来指定对SortBy
, SortByDescending
, ThenBy
和ThenByDescending
FLUENT接口的方法。按照前面的示例,这将被定义为:
await collection.Find(FilterDefinition<Student>.Empty)
.Skip((currentPage - 1) * pageSize)
.Limit(pageSize)
.SortByDescending(x => x.LastName)
.ThenBy(x => x.Age)
.ForEachAsync(
student =>
{
Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
count++;
});
大多数情况下,我们将使用强类型对象,因为使用表达式树构建查询要容易得多。
Projection投影
我们也可以使用fluent接口的Project
方法进行投影。我们指定一个类似于排序和过滤的投影。
使用表达式树或投影定义会导致稍微不同的行为。不同之处之一是,在使用投影定义语法时,必须明确地告诉它排除_id
字段,否则,它会将其作为结果集的一部分返回。让我们更新代码,只返回FirstName
await collection.Find(FilterDefinition<Student>.Empty)
.Skip((currentPage - 1) * pageSize)
.Limit(pageSize)
.SortByDescending(x => x.LastName)
.ThenBy(x => x.Age)
.Project("{FirstName: 1}")
.ForEachAsync(
student =>
{
Debug.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
count++;
});
使用更新的代码,我们的应用程序无法编译。给我们带来了另一个区别:通过投影定义,它隐式地将文档类型从Student
转换为bsondocument
,因此我们得到的是一个fluent对象,其结果将是一个BsonDocument
(即使我们使用的是Student类型)。如果我们想和Student一起工作,我们必须指出我们仍然希望将类型保留为Student
。
.Project<Student>("{FirstName: 1}")
因此,通过将Student
设置为方法的类型来更新我们的代码,将得到以下输出:
Page 1
S/N: 1, Id: 58469c732adc9f5370e50c9e, FirstName: Julie, LastName: , Age: 0
S/N: 2, Id: 58469c732adc9f5370e50c9c, FirstName: Gregor, LastName: , Age: 0
Page 2
S/N: 1, Id: 58469c732adc9f5370e50c9d, FirstName: Machiko, LastName: , Age: 0
S/N: 2, Id: 58469c732adc9f5370e50ca0, FirstName: James, LastName: , Age: 0
Page 3
S/N: 1, Id: 58469c732adc9f5370e50c9f, FirstName: Peter, LastName: , Age: 0
您可以看到,虽然我们只需要FirstName
,但是FirstName
和Id
被返回,而其他的则保持默认值。为了解决这个问题,我们显式地告诉它排除Id字段,并对投影定义进行以下更新:
.Project<Student>("{FirstName: 1, _id: 0}")
然后运行它,我们只返回FirstName
,而其他值保持默认值:
Page 1
S/N: 1, Id: 000000000000000000000000, FirstName: Julie, LastName: , Age: 0
S/N: 2, Id: 000000000000000000000000, FirstName: Gregor, LastName: , Age: 0
Page 2
S/N: 1, Id: 000000000000000000000000, FirstName: Machiko, LastName: , Age: 0
S/N: 2, Id: 000000000000000000000000, FirstName: James, LastName: , Age: 0
Page 3
S/N: 1, Id: 000000000000000000000000, FirstName: Peter, LastName: , Age: 0
我们也可以使用投影生成器。.Project<Student>(Builders<Student>.Projection.Include(x => x.FirstName).Exclude(x => x.Id))
这与使用定义生成器进行排序和筛选类似。我们也可以使用表达式树进行投影,然后将其投影到不同的结果。以下代码将只返回first 和last name,并将其映射到匿名类型:
int count = 1;
await collection.Find(FilterDefinition<Student>.Empty)
.Project(x => new {x.FirstName, x.LastName})
.ForEachAsync(
student =>
{
Console.WriteLine($"{count}. \t FirstName: {student.FirstName} - LastName {student.LastName}");
count++;
});
Console.WriteLine();
1. FirstName: Gregor - LastName Felix
2. FirstName: Machiko - LastName Elkberg
3. FirstName: Julie - LastName Sandal
4. FirstName: Peter - LastName Cyborg
5. FirstName: James - LastName Cyborg
您可能已经注意到,我们并没有显式地指明要排除Id,而是与另一种方式不同,这是因为在强类型表达式树中,它同意只返回您指定的那些字段,而排除其他字段。
总结
本文带着你一起研究了一下文档的排序,指定要跳过或限制返回的文档数量,以及如何进行投影。此篇文章中的实例代码摘录自原文,未像前几篇文章一样进行实际代码的验证。希望对你有所帮助。
在.NET Core中使用MongoDB明细教程(3):Skip, Sort, Limit, Projections的更多相关文章
- 在.NET Core中使用MongoDB明细教程(1):驱动基础及文档插入
MongoDB,被归类为NoSQL数据库,是一个以类JSON格式存储数据的面向文档的数据库系统.MongoDB在底层以名为bson的二进制编码格式表示JSON文档,MongoDB bson实现是轻量级 ...
- 在.NET Core中使用MongoDB明细教程(2):使用Filter语句检索文档
在上篇文章我们介绍了一些驱动程序相关的基础知识,以及如何将文档插入到集合中.在这篇文章中,我们将学习如何从数据库中检索文档. 作者:依乐祝 译文地址:https://www.cnblogs.com/y ...
- 在.Net Core中使用MongoDB的入门教程(二)
在上一篇文章中,讲到了MongoDB在导入驱动.MongoDB的连接,数据的插入等. 在.Net Core中使用MongoDB的入门教程(一) 本篇文章将接着上篇文章进行介绍MongoDB在.Net ...
- 在.Net Core中使用MongoDB的入门教程(一)
首先,我们在MongoDB的官方文档中看到,MongoDb的2.4以上的For .Net的驱动是支持.Net Core 2.0的. 所以,在我们安装好了MangoDB后,就可以开始MangoDB的.N ...
- Asp.Net Core中使用MongoDB的入门教程,控制台程序使用 MongoDB
内容来源 https://blog.csdn.net/only_yu_yy/article/details/78882446 首先,创建一个.Net Core的控制台应用程序.然后使用NuGet导入 ...
- 【Java必修课】图说Stream中的skip()和limit()方法及组合使用
1 简介 本文将讲解Java 8 Stream中的两个方法:skip()和limit().这两个方法是Stream很常用的,不仅各自会被高频使用,还可以组合出现,并能实现一些小功能,如subList和 ...
- .Net Core中简单使用MongoDB
MongoDB 是由C++语言编写的,是一个基于分布式且面向文档存储的开源数据库系统. 下载地址: https://www.mongodb.com/download-center/community ...
- C# .NET Core 3.1中使用 MongoDB.Driver 更新嵌套数组元素和关联的一些坑
C# .NET Core 3.1中使用 MongoDB.Driver 更新数组元素和关联的一些坑 前言: 由于工作的原因,使用的数据库由原来的 关系型数据库 MySQL.SQL Server 变成了 ...
- ASP.NET Core教程:ASP.NET Core中使用Redis缓存
参考网址:https://www.cnblogs.com/dotnet261010/p/12033624.html 一.前言 我们这里以StackExchange.Redis为例,讲解如何在ASP.N ...
随机推荐
- Java数据结构和算法(2)之稀疏数组
1.定义 稀疏数组可以看做是普通二位数组的压缩,但是这里说的普通数组是值无效数据量远大于有效数据量的数组,关于稀疏数组的运用有五子棋盘,地图等.. *当一个数组中大部分元素为0,或者为同一个值的数组时 ...
- Django学习路16_获取学生所在的班级名
在 urls.py 中先导入getgrades from django.conf.urls import url from app5 import views urlpatterns = [ url( ...
- Seaborn基础3
import seaborn as sns import numpy as np import matplotlib.pyplot as plt sns.set(rc = {"figure. ...
- PHP easter_days() 函数
------------恢复内容开始------------ 实例 输出不同年份的复活节与 3 月 21 日之间的天数: <?phpecho "Easter Day is " ...
- PHP fclose() 函数
定义和用法 fclose() 函数关闭打开的文件. 该函数如果成功则返回 TRUE,如果失败则返回 FALSE. 语法 fclose(file) 参数 描述 file 必需.规定要关闭的文件. 实例 ...
- PHP tempnam() 函数
定义和用法 tempnam() 函数在指定的目录中创建一个具有唯一文件名的临时文件. 该函数返回新的临时文件名,如果失败则返回 FALSE. 语法 tempnam(dir,prefix) 参数 描述 ...
- luogu P5289 [十二省联考2019]皮配 背包
LINK:皮配 我承认是一道很难的题目. 不过对于这道题 部分分的提示显得尤为重要. 首先是 40分的暴力dp 很容易想 但是不容易写. 从40分可以发现我们只需要把蓝阵营和鸭派系的人数给存在起来就行 ...
- IntelliJ IDEA 控制台输出中文乱码
IntelliJ IDEA 控制台输出中文乱码部分如图所示: 解决方法一: 1.打开IntelliJ IDEA本地安装目录中bin文件夹下的idea.exe.vmoptions和idea64.exe. ...
- 用大白话解释什么是Socket
好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 前言 我在去年就学习过Java中 ...
- Spring学习总结(2)-自动装配
上面说过,IOC的注入有两个地方需要提供依赖关系,一是类的定义中,二是在spring的配置中需要去描述.自动装配则把第二个取消了,即我们仅仅需要在类中提供依赖,继而把对象交给容器管理即可完成注入.在实 ...