在.NET Core中使用MongoDB明細教程(2):使用Filter語句檢索文件

依樂祝發表於2020-08-18

在上篇文章我們介紹了一些驅動程式相關的基礎知識,以及如何將文件插入到集合中。在這篇文章中,我們將學習如何從資料庫中檢索文件。

作者:依樂祝

譯文地址: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", "tags" : [".net core", "mongodb"], "remark" : "C#是世界上最好的語言", "publishyear" : 2020 }

{ "_id" : ObjectId("5f3367482d2d59d358e1219c"), "bookname" : ".net core3.1 with mongodb2", "description" : "這是一本關於在.net core3.1中使用mongodb進行開發的教程2", "tags" : [".net core", "mongodb"], "remark" : "C#是世界上最好的語言", "publishyear" : 2020 }

{ "_id" : ObjectId("5f3367482d2d59d358e1219d"), "bookname" : ".net core3.1 with mongodb2", "description" : "這是一本關於在.net core3.1中使用mongodb進行開發的教程2", "tags" : [".net core", "mongodb"], "remark" : "C#是世界上最好的語言", "publishyear" : 2020 }

{ "_id" : ObjectId("5f33850d467bf3877966f1ea"), "BookName" : ".net core3.1 with mongodb21", "Description" : "這是一本關 於在.net core3.1中使用mongodb進行開發的教程21", "Tags" : [".net core", "mongodb"], "Remark" : "C#是世界上最好的語言", "PublishYear" : 2020 }

{ "_id" : ObjectId("5f33850d467bf3877966f1eb"), "BookName" : ".net core3.1 with mongodb22", "Description" : "這是一本關 於在.net core3.1中使用mongodb進行開發的教程22", "Tags" : [".net core", "mongodb"], "Remark" : "C#是世界上最好的語言", "PublishYear" : 2020 }

{ "_id" : ObjectId("5f33850d467bf3877966f1ec"), "BookName" : ".net core3.1 with mongodb23", "Description" : "這是一本關 於在.net core3.1中使用mongodb進行開發的教程23", "Tags" : [".net core", "mongodb"], "Remark" : "C#是世界上最好的語言", "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"), "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", "tags" : [".net core", "mongodb"], "remark" : "C#是世界上最好的語言", "publishyear" : 2020 }

Batch: 2
{ "_id" : ObjectId("5f3367482d2d59d358e1219c"), "bookname" : ".net core3.1 with mongodb2", "description" : "這是一本關於在.net core3.1中使用mongodb進行開發的教程2", "tags" : [".net core", "mongodb"], "remark" : "C#是世界上最好的語言", "publishyear" : 2020 }

{ "_id" : ObjectId("5f3367482d2d59d358e1219d"), "bookname" : ".net core3.1 with mongodb2", "description" : "這是一本關於在.net core3.1中使用mongodb進行開發的教程2", "tags" : [".net core", "mongodb"], "remark" : "C#是世界上最好的語言", "publishyear" : 2020 }

Batch: 3
{ "_id" : ObjectId("5f33850d467bf3877966f1ea"), "BookName" : ".net core3.1 with mongodb21", "Description" : "這是一本關 於在.net core3.1中使用mongodb進行開發的教程21", "Tags" : [".net core", "mongodb"], "Remark" : "C#是世界上最好的語言", "PublishYear" : 2020 }

{ "_id" : ObjectId("5f33850d467bf3877966f1eb"), "BookName" : ".net core3.1 with mongodb22", "Description" : "這是一本關 於在.net core3.1中使用mongodb進行開發的教程22", "Tags" : [".net core", "mongodb"], "Remark" : "C#是世界上最好的語言", "PublishYear" : 2020 }

Batch: 4
{ "_id" : ObjectId("5f33850d467bf3877966f1ec"), "BookName" : ".net core3.1 with mongodb23", "Description" : "這是一本關 於在.net core3.1中使用mongodb進行開發的教程23", "Tags" : [".net core", "mongodb"], "Remark" : "C#是世界上最好的語言", "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"), "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", "tags" : [".net core", "mongodb"], "remark" : "C#是世界上最好的語言", "publishyear" : 2020 }
{ "_id" : ObjectId("5f3367482d2d59d358e1219c"), "bookname" : ".net core3.1 with mongodb2", "description" : "這是一本關於在.net core3.1中使用mongodb進行開發的教程2", "tags" : [".net core", "mongodb"], "remark" : "C#是世界上最好的語言", "publishyear" : 2020 }
{ "_id" : ObjectId("5f3367482d2d59d358e1219d"), "bookname" : ".net core3.1 with mongodb2", "description" : "這是一本關於在.net core3.1中使用mongodb進行開發的教程2", "tags" : [".net core", "mongodb"], "remark" : "C#是世界上最好的語言", "publishyear" : 2020 }
{ "_id" : ObjectId("5f33850d467bf3877966f1ea"), "BookName" : ".net core3.1 with mongodb21", "Description" : "這是一本關 於在.net core3.1中使用mongodb進行開發的教程21", "Tags" : [".net core", "mongodb"], "Remark" : "C#是世界上最好的語言", "PublishYear" : 2020 }
{ "_id" : ObjectId("5f33850d467bf3877966f1eb"), "BookName" : ".net core3.1 with mongodb22", "Description" : "這是一本關 於在.net core3.1中使用mongodb進行開發的教程22", "Tags" : [".net core", "mongodb"], "Remark" : "C#是世界上最好的語言", "PublishYear" : 2020 }
{ "_id" : ObjectId("5f33850d467bf3877966f1ec"), "BookName" : ".net core3.1 with mongodb23", "Description" : "這是一本關 於在.net core3.1中使用mongodb進行開發的教程23", "Tags" : [".net core", "mongodb"], "Remark" : "C#是世界上最好的語言", "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. 。

相關文章