在 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
跳过
如果我们想告诉数据库要跳过多少个文档,可以使用Skip
Fluent 接口中的方法。这和之前的代码类似,但需要告诉数据库返回所有年龄小于 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 接口的方法进行投影。我们指定一个投影,类似于sort和filter 的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,但与其他方式不同,这是因为使用强类型表达式树,它同意仅返回您指定的那些字段并排除其他字段。
本系列之前的教程:
- 在 C# .NETCore 中使用 MongoDB(第 1 部分):驱动程序基础知识和插入文档
- 在 C# .NETCore 中使用 MongoDB(第 2 部分):使用过滤子句检索文档
如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。