在 C# .NETCore 中使用 MongoDB(第 3 部分):跳过、排序、限制和投影

到目前为止,我们已经了解了创建文档检索文档,现在让我们了解如何对文档进行排序、指定要跳过或限制的文档数量以及如何进行投影。

在 C# .NETCore 中使用 MongoDB(第 1 部分):驱动程序基础知识和插入文档:
https://blog.csdn.net/hefeng_aspnet/article/details/150556600

在 C# .NETCore 中使用 MongoDB(第 2 部分):使用过滤子句检索文档:
https://blog.csdn.net/hefeng_aspnet/article/details/150557025

在 C# .NETCore 中使用 MongoDB(第 3 部分):跳过、排序、限制和投影:
https://blog.csdn.net/hefeng_aspnet/article/details/150557338

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

限制

当我们查询文档时,有时我们并不想返回所有符合过滤条件的文档,而只想返回其中一部分。这时,限制子句就派上用场了。使用 MongoDB,您可以通过调用 的返回Limit方法来限制文档数量。因此,如果我在数据库中查询年龄小于 40 岁的学生,则会得到以下结果:IFindFluent``Find

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

跳过

如果我们想告诉数据库要跳过多少个文档,可以使用SkipFluent 接口中的方法。这和之前的代码类似,但需要告诉数据库返回所有年龄小于 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流式接口的方法接受一个,SortDefinition它可以像 一样从字符串或 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}where1来指示按升序排序和-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

如果我们想LastName使用 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

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++;

});

大多数时候,我们会使用强类型对象,因为使用表达式树构建查询要容易得多。

投影

我们也可以使用 Fluent 接口的方法进行投影。我们指定一个投影,类似于sortfilter 的 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,因此我们返回的是一个流式对象,最终结果将是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)),它类似于使用定义构建器进行排序和过滤。我们还可以使用表达式树进行投影,然后将其投影到不同的结果。以下代码将仅返回姓氏和名字,并将其映射到匿名类型:

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,但与其他方式不同,这是因为使用强类型表达式树,它同意仅返回您指定的那些字段并排除其他字段。

本系列之前的教程:

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

相关推荐
emplace_back5 小时前
C# 主窗口 单例 唯一进程 互斥锁
开发语言·c#
ajassi20006 小时前
开源 C# 快速开发(七)通讯--串口
开源·c#·mfc
熊文豪6 小时前
政务系统国产化的“教科书级“实践:MongoDB平滑迁移成功案例解析
数据库·mongodb·政务·kingbasees·金仓数据库
爱吃小胖橘6 小时前
Unity-动画IK控制
3d·unity·c#·游戏引擎
时光追逐者15 小时前
一个基于 .NET 开源、简易、轻量级的进销存管理系统
开源·c#·.net·.net core·经销存管理系统
诺青23516 小时前
MongoDB副本集
数据库·mongodb
William_cl16 小时前
【连载7】 C# MVC 跨框架异常处理对比:.NET Framework 与 .NET Core 实现差异
c#·mvc·.net
ajassi200016 小时前
开源 C# 快速开发(五)自定义控件--仪表盘
开发语言·开源·c#
正在走向自律16 小时前
金仓数据库打通电子证照国产化“最后一公里”——福建某地2TB MongoDB无缝迁移实践
数据库·mongodb·国产数据库·电科金仓