在.NET Core中使用MongoDB明细教程(2):使用Filter语句检索文档

在上篇文章我们介绍了一些驱动程序相关的基础知识,以及如何将文档插入到集合中。在这篇文章中,我们将学习如何从数据库中检索文档。

作者:依乐祝

译文地址:https://www.cnblogs.com/yilezhu/p/13520021.html

英文地址:https://www.codementor.io/@pmbanugo/working-with-mongodb-in-net-2-retrieving-mrlbeanm5

任何文档都属于集合,因此所有CRUD操作都是在单个集合范围中完成的。若要从集合中检索文档,可以使用Find,FindSync,和FindAsync等方法。

FindSync&FindAsync

FindSyncFindAsync两者都有两个带有三个参数的重载。FindSyncFindAsync很相似,只是FindSync是同步的,并阻塞直到它的调用完成。FindSync返回IAsyncCursor ,而FindAsync返回一个IAsyncCursor的任务.

什么是IAsyncCursor

MongoDB以批形式返回查询结果,批处理大小不会超过BSON文档的最大大小。从版本3.2开始,BSON文档的最大大小为16 MB。最大文档大小有助于确保单个文档在传输过程中不能使用过多的RAM或过多的带宽。此约束在将文档添加到集合时也适用,但是为了存储更大的文档,MongoDB已经将GridFS API作为一项规定。对于大多数查询,第一批将返回101个文档或刚好超过1MB的文档,随后的批处理将为4MB。我们可以在驱动程序中通过设置FindOptionsBatchSize 属性来覆盖默认的批大小,该属性作为第二个参数传递给任何find方法。所以基本上,游标是指向查询结果集的指针。

默认情况下,服务器将在不活动10分钟后或客户端耗尽游标后自动关闭游标。若要重写此行为,可以指定在查询中使用FindOptions类的NoCursorTimeout属性值设置为false。但是,如果你这样做您应该手动关闭游标或耗尽游标。

驱动程序中的这个IAsyncCursor表示异步游标。要访问文档,我们需要手动迭代游标。

检索文件

让我们构建我们的第一个Read查询,这个查询返回我们数据库中books中的所有数据。更新MainAsync方法如下:

static async Task Main(string[] args)
        {
            await TestFindAsync();
            Console.ReadLine();
        }

        static async Task TestFindAsync()
        {
            var connectionString = "mongodb://localhost:27017";
            var client = new MongoClient(connectionString);
            var database = client.GetDatabase("bookstore");
            var collection = database.GetCollection<BsonDocument>("books");
            using IAsyncCursor<BsonDocument> cursor = await collection.FindAsync(new BsonDocument());
            while (await cursor.MoveNextAsync())
            {
                IEnumerable<BsonDocument> batch = cursor.Current;
                foreach (BsonDocument document in batch)
                {
                    Console.WriteLine(document);
                    Console.WriteLine();
                }
            }
        }

任何find方法的第一个重载都有3个参数:FilterDefinition (用于定义查询的筛选器)、一个可选的FindOptions(用于指定查询的选项(例如游标超时、批处理大小等)和一个可选的cancellationToken

在上面的代码中,我们通过向方法传递一个空的BsonDocument来指定一个空的过滤器定义。另一种编写方法是使用FilterDefinition<BsonDocument>.Empty来表示一个空的过滤器。有了空过滤器,我们基本上是告诉它返回给我们集合中的所有文档。然后,我们迭代游标以成批获取文档(while循环中的MoveNextAsync),并调用cursor.Current获取当前批中的文档,然后将其打印出来。

运行上面的代码应该可以为我们提供该集合中已有的所有文档

{ "_id" : ObjectId("5f33630f9e7b20e7e29208f3"),"bookname" : ".net core3.1 with mongodb","description" : "这是一本关于 在.net core3.1中使用mongodb进行开发的教程","tags" : [".net core","mongodb"],"remark" : "C#是世界上最好的语言","publishyear" : 2020 }

{ "_id" : ObjectId("5f3367482d2d59d358e1219b"),"bookname" : ".net core3.1 with mongodb1","description" : "这是一本关于在.net core3.1中使用mongodb进行开发的教程1","publishyear" : 2020 }

{ "_id" : ObjectId("5f3367482d2d59d358e1219c"),"bookname" : ".net core3.1 with mongodb2","description" : "这是一本关于在.net core3.1中使用mongodb进行开发的教程2","publishyear" : 2020 }

{ "_id" : ObjectId("5f3367482d2d59d358e1219d"),"publishyear" : 2020 }

{ "_id" : ObjectId("5f33850d467bf3877966f1ea"),"BookName" : ".net core3.1 with mongodb21","Description" : "这是一本关 于在.net core3.1中使用mongodb进行开发的教程21","Tags" : [".net core","Remark" : "C#是世界上最好的语言","PublishYear" : 2020 }

{ "_id" : ObjectId("5f33850d467bf3877966f1eb"),"BookName" : ".net core3.1 with mongodb22","Description" : "这是一本关 于在.net core3.1中使用mongodb进行开发的教程22","PublishYear" : 2020 }

{ "_id" : ObjectId("5f33850d467bf3877966f1ec"),"BookName" : ".net core3.1 with mongodb23","Description" : "这是一本关 于在.net core3.1中使用mongodb进行开发的教程23","PublishYear" : 2020 }

我们可以看到返回的数据跟我们在上一篇文章中添加的文档基本一样,除了多了一个_id属性,所有集合在这个字段上都有一个唯一的主索引,如果您在创建文档时没有提供主索引,那么MongoDB会默认提供一个主索引。它的类型是ObjectId,这是在Bson规范中定义。

为了演示FindOptions,我将添加一个将批大小限制为2的选项,该选项将显示我们在控制台中循环的批。使用以下内容更新代码

static async Task Main(string[] args)
        {
            await TestFindAsync();
            Console.ReadLine();
        }

        static async Task TestFindAsync()
        {
            var connectionString = "mongodb://localhost:27017";
            var client = new MongoClient(connectionString);
            var database = client.GetDatabase("bookstore");
            var collection = database.GetCollection<BsonDocument>("books");
            FilterDefinition<BsonDocument> filter = FilterDefinition<BsonDocument>.Empty;
            FindOptions<BsonDocument> options = new FindOptions<BsonDocument> {
                BatchSize = 2,NoCursorTimeout = false
            };
            using IAsyncCursor<BsonDocument> cursor = await collection.FindAsync(filter,options);
            var batch = 0;
            while (await cursor.MoveNextAsync())
            {
                batch++;
                Console.WriteLine($"Batch: {batch}");
                IEnumerable<BsonDocument> documents = cursor.Current;
                foreach (BsonDocument document in documents)
                {
                    Console.WriteLine(document);
                    Console.WriteLine();
                }
            }
            Console.WriteLine($"Total Batch: { batch}");
        }

并运行它以获得以下结果:

Batch: 1
{ "_id" : ObjectId("5f33630f9e7b20e7e29208f3"),"publishyear" : 2020 }

Batch: 2
{ "_id" : ObjectId("5f3367482d2d59d358e1219c"),"publishyear" : 2020 }

Batch: 3
{ "_id" : ObjectId("5f33850d467bf3877966f1ea"),"PublishYear" : 2020 }

Batch: 4
{ "_id" : ObjectId("5f33850d467bf3877966f1ec"),"PublishYear" : 2020 }

Total Batch: 4

我们还可以通过调用ToListAsyncForEachAsync从光标中获取所有文档并将它们放入内存中,从而以更简洁的方式编写此代码。在IAsyncCursor上有扩展方法可以这么做。下面是一些代码:

collection.FindSync(filter).ToList();

await collection.FindSync(filter).ToListAsync();

await collection.FindSync(filter).ForEachAsync(doc => Console.WriteLine());

collection.FindSync(filter).FirstOrDefault();

collection.FindSync(filter).FirstOrDefault();

await collection.FindSync(filter).FirstOrDefaultAsync();

从代码角度看,这看起来既简洁又简短,但它所做的是强制所有文档都保存在内存中。在某些情况下,这可能不太理想,当查询结果很大时,游标很有用,我们可以通过调用MoveNextAsyncMoveNext来移动光标。

Find

此方法与其对应方法相似,但它返回IFindFluent接口。这是一个流畅的接口,它为我们提供了一些简单的语法:Count,Skip,Sort,和Limit(关于这些,下篇文章中会有更多的介绍)。从IFindFluent中我们也可以返回一个游标(通过调用ToCursorToCursorAsync或一个列表(通过调用ToListToListAsync)。通过下面的代码,我们可以使用Find方法获取所有文档并将它们打印到控制台

await collection.Find(FilterDefinition<BsonDocument>.Empty)
        .ForEachAsync(doc => Console.WriteLine(doc));

结果

{ "_id" : ObjectId("5f33630f9e7b20e7e29208f3"),"publishyear" : 2020 }
{ "_id" : ObjectId("5f3367482d2d59d358e1219b"),"publishyear" : 2020 }
{ "_id" : ObjectId("5f3367482d2d59d358e1219c"),"publishyear" : 2020 }
{ "_id" : ObjectId("5f3367482d2d59d358e1219d"),"publishyear" : 2020 }
{ "_id" : ObjectId("5f33850d467bf3877966f1ea"),"PublishYear" : 2020 }
{ "_id" : ObjectId("5f33850d467bf3877966f1eb"),"PublishYear" : 2020 }
{ "_id" : ObjectId("5f33850d467bf3877966f1ec"),"PublishYear" : 2020 }

查找特定的文件

大多数情况下,我们不想检索所有文档,而是指定一个筛选器,它返回与特定筛选器匹配的文档。现在让我们看看为查询指定筛选器的方法。

使用BsonDocumentString

我们可以将BsonDocument定义为一个过滤器,查询将找到与文档中定义的字段相匹配的文档。将以下代码添加到您的方法中,并运行它以检索description为“这是一本关于在.net core3.1中使用mongodb进行开发的教程1”的书籍

var filter = new BsonDocument("description","这是一本关于在.net core3.1中使用mongodb进行开发的教程1");
            await collection.Find(filter).ForEachAsync(doc => Console.WriteLine(doc));

这样只返回符合条件的一个文档

var filter = new BsonDocument("description","这是一本关于在.net core3.1中使用mongodb进行开发的教程1");
            await collection.Find(filter).ForEachAsync(doc => Console.WriteLine(doc));

您可能会有些困惑,因为这些方法接受FilterDefinition,但是我们给了它一个BsonDocument,它没有出异常。之所以会发生这种情况,是因为它被隐式转换,而且我们也可以通过字符串进行转换。要使用字符串,我们需要定义一个有效的JSON字符串来指定过滤器。我们可以使用下面的代码对字符串执行上述相同的操作,仍然会得到相同的结果:

var filter= "{description:'这是一本关于在.net core3.1中使用mongodb进行开发的教程1'}";
            await collection.Find(filter).ForEachAsync(doc => Console.WriteLine(doc));

我们也可以指定比较或逻辑运算符。例如,查找2020年出版的书。我们可以如下所示构建查询:

var filter = new BsonDocument("publishyear",new BsonDocument("$eq",2020));

或者使用字符串

var filter = "{ Age: {'$eq': 23}}";

我们所做的是为操作符添加一个标识符,在我们的例子中是$eq

使用FilterDefinitionBuilder

您可以使用FilterDefinitionBuilder,它是FilterDefinition的构建器。它提供了一套方法来构建查询,而Lt作为其中之一,指定了小于比较。因此,我们可以使用FilterDefinitionBuilder定义过滤器,如下所示:

var filter = new FilterDefinitionBuilder<BsonDocument>().Lt("publishyear",2020);

或者使用接受LINQ表达式的重载方法:

var filter = new FilterDefinitionBuilder<Book>().Lt( book => book.PublishYear,2020);

此外,您还可以使用静态Builders类来构建过滤器定义,该类还具有用于构建其他内容的静态帮助方法,如投影定义、排序定义和其他一些方法。

var filter = Builders<BsonDocument>.Filter.Lt("publishyear",2020);
var filter = Builders<Book>.Filter.Lt(book => book.PublishYear,2020);

驱动程序还为过滤器定义重载了3个操作符。这个and (&),or (|)not (!)操作。例如,我们希望得到出版年份是2020年且描述信息为这是一本关于在.net core3.1中使用mongodb进行开发的教程1的书籍信息,我们可以使用构建器帮助方法和&重载操作符如下

var builder = Builders<BsonDocument>.Filter;
var filter = builder.Eq("publishyear",2020) & builder.Eq("description","`这是一本关于在.net core3.1中使用mongodb进行开发的教程1");

Linq表达 式

最后一部分我们没有讨论的是这些方法的重载,这些方法采用LINQ表达式,当我们有一个强类型对象时,我们可以使用LINQ表达式构建一个过滤器查询。假设我们想让出版年份为2020,描述信息为这是一本关于在.net core3.1中使用mongodb进行开发的教程1的书籍信息打印出他们。我们使用以下代码:

  var collection = database.GetCollection<Book>("books");
            await collection.Find(book => book.PublishYear == 2020 && book.Description == "这是一本关于在.net core3.1中使用mongodb进行开发的教程1").ForEachAsync(doc => Console.WriteLine(doc));

我们将集合类型更改为Book并运行以下代码,我们将在控制台上得到一个错误:

错误描述显示_id不匹配任何类型的字段或属性。这是因为Book对象无法映射_id字段从数据库到“学生”类型的任何属性。这是在我们创建文档时自动添加的。让我们通过在Book类中添加Bson类型的ID属性。

internal class Book
    {
        public ObjectId Id { get; set; }
        public string BookName { get; set; }
        public string Description { get; set; }
        public IEnumerable<string> Tags { get; set; }
        public string Remark { get; set; }
        public int PublishYear { get; set; }
    }

并运行发现正常了。当然你也可以通过设置IgnoreExtraElement为true来规避这个问题

它只是起作用了。所以很多时候,你会想用表达式树语法来构建您的查询。在需要更多粒度的情况下,可以使用其他方法。

在下一个教程中,我们将看到如何进行 projections,sort,skip,limit and sort. 。

相关文章

本文将从上往下,循序渐进的介绍一系列相关.NET的概念,先从...
基于 .NET 的一个全新的、好用的 PHP SDK + Runtime: Pe...
.NET 异步工作原理介绍。
引子 .NET 6 开始初步引入 PGO。PGO 即 Profile Guided Opti...
前言 2021/4/8 .NET 6 Preview 3 发布,这个版本的改进大多来...
前言 开头防杠:.NET 的基础库、语言、运行时团队从来都是相...