初識 MongoDB 和 .NET Core 入門

痴者工良發表於2020-10-18


昨天搭建完畢 MongoDB 叢集 後,開始計劃瞭解 MongoDB ,並引入使用場景,這裡介紹一下學習過程中的一些筆記,幫助讀者快速瞭解 MongoDB 並使用 C# 對其進行編碼。

淺入 MongoDB

MonogoDB 是什麼

MongoDB 是 NoSQL 型資料庫,主要特徵是儲存結構化資料,MongoDB 是基於分散式檔案儲存的開源資料庫系統。

結構化資料

以往我們使用 Mysql、SqlServer 等資料庫,資料都是一條條的。MongoDB 的結構化資料正是區別於這種列-行式的資料。

結構化資料具有層級關係:

例如:

{
     name: "MongoDB",
     type: "database",
     count: 1,
     info: {
         x: 203,
         y: 102
     }
}

結構化資料

MongoDB 與關係型資料庫

由於 MongoDB 中,沒有表、行、列,因此初學 MongoDB 時可能會有困擾,這裡給出一些 MongoDB 與 普通SQL資料庫對應的術語。

SQL術語/概念 MongoDB術語/概念 解釋/說明
database database 資料庫
table collection 資料庫表/集合
row document 資料記錄行/文件
column field 資料欄位/域
index index 索引
table joins 非關係型資料庫,表與表之間沒關係
primary key primary key 主鍵,MongoDB自動將_id欄位設定為主鍵

資料來源:https://www.runoob.com/mongodb/mongodb-databases-documents-collections.html

MongoDB 入門命令

使用 mongo 進入 MongoDB shell 後,可使用命令(相當於SQL)執行操作。

注: MongoDB 中,有一個自動的 _id 欄位,此欄位 MongoDB 自動設定為主鍵並自動生成值。

顯示所有資料庫(包含系統資料庫):

show dbs

當前正在操作的資料庫或集合:

db

連線到指定資料庫:

use {資料庫名稱}

顯示所有集合:

show collections
# 或
show tables

檢視集合中的所有文件:

# MyCollection 是集合名稱
db.getCollection("MyCollection").find()
db.getCollection("MyCollection").find().limit(1000).skip(0)

可能你不信,筆者百度了很久,第一頁沒找到一篇合適的友好的 "mongoDB 檢視集合中的所有文件",特別是 CSDN 的垃圾文真的多。建議別瞎折騰了,去下一個 Navicat Premium,操作的時候,底部會提示所用的命令。

使用工具檢視MongoDB命令

另外 MongoDB 有很多實用工具:https://docs.mongodb.com/tools/

文件

MongoDB 中的文件(Document)即關係型資料庫中的一條記錄(row)、一行資料。

但, MongoDB 中,一個集合(Collection-Table)中,是不需要具有相同欄位的。例如:

A 文件:

{
     name: "MongoDB",
     type: "database",
     count: 1,
     info: {
         x: 203,
         y: 102
     }
}

B 文件:

{
     name: "MongoDB",
     typeName: "database",
     dataCount: 1,
     dataInfo: {
         m: 203,
         n: 102
     }
}

.NET Core 示例

我們從一個基礎模板開始。

建立一個控制檯程式,開啟 Nuget 搜尋並安裝 MongoDB.Driver

            var client = new MongoClient("mongodb://{MongoDB}:27017");
            IMongoDatabase database = client.GetDatabase("Test");

集合

可以通過 CreateCollection()CreateCollectionAsync() 建立一個集合,跟普通資料庫不同的是,建立集合時是不需要指定結構的,只需要指定名稱即可:

await database.CreateCollectionAsync("Test");

獲取集合

GetCollection() 函式可以讓我們獲取到集合,如果集合不存在,則會自動建立。

IMongoCollection<TDocument> GetCollection<TDocument>()

由於同一個集合可以有不同欄位和欄位型別的文件,因此幾個文件如果有所差別,是很難統一起來的,例如:

集合的文件欄位不同

(N/A) 代表此文件沒有這個欄位;如果一個文件有 10 個欄位,另一個文件有 8 個欄位,但是兩者的欄位完全不同時,要合併起來來,就有 18 個欄位了。很明顯,不應該彙集在一起,而是應該使用強型別對其 ”歸檔“ 。

建立兩個類,分別為 Test1,Test2,其內容如下:

    public class Test1
    {
        public string Name { get; set; }
    }

    public class Test2
    {
        public string DataType { get; set; }
    }

以兩種文件型別獲取集合:

            var collection1 = database.GetCollection<Test1>("Test");
            var collection2 = database.GetCollection<Test2>("Test");

這個獲取集合的意思是,獲取此集合中這類格式的文件的操作能力。

往集合中插入資料:

            collection1.InsertOne(new Test1 { Name = "Test1" });
            collection2.InsertOne(new Test2 { DataType = "Test2" });
			// await collection.InsertOneAsync(object);

啟動,檢視結果。

InsertMany() 可以插入批量資料:

            Test1[] datas = new Test1[]
            {
                new Test1 { Name = "Test1" }
            };
            collection1.InsertMany(datas);

統計數量

獲取集合中所有的文件數:

collection1.CountDocuments(new BsonDocument())
// await collection1.CountDocumentsAsync(new BsonDocument());

任意一個文件集合物件,使用 CountDocuments(new BsonDocument()) 都是獲得此集合的所有文件數,而不是此型別的文件數。例如:

            var collection1 = database.GetCollection<Test1>("Test");
            collection1.CountDocuments(new BsonDocument())

獲取的並不是 Test1 型別的文件數量,而是整個集合所有文件的數量。

原因是,CountDocuments() 是一個過濾器函式,可以使用指定條件來篩選符合條件的文件的數量。指定條件後面會介紹。

查詢

MongoDB 的查詢並不像 LInq 中的表示式,基礎了 IEnumerableIEnumerable<T> 介面,因此驅動沒有 WhereSelect 這種表示式的查詢方法。

Find() 函式是查詢函式,裡面可以新增豐富的表示式,來篩選文件,當資料載入到本地記憶體後,即可使用豐富的表示式。

BsonDocument 是一個型別,代表了要查詢的文件篩選條件,如果 BsonDocument 物件沒有新增任何屬性,則程式碼沒有篩選引數,則預設所有文件都符號條件。

我們把 Test1 和 Test2 型別,都加上一個屬性:

        public ObjectId _id { get; set; }

不然會報格式化錯誤:System.FormatException

如何序列化文件

document 是文件物件, JsonSerializer 是 System.Text.Json 的靜態類。

Console.WriteLine(JsonSerializer.Serialize(document));

查詢第一條記錄

var document = collection1.Find(new BsonDocument()).FirstOrDefault();

不加條件可能導致的問題

以下程式碼會導致程式報錯:

            var documents = await collection1.Find(new BsonDocument()).ToListAsync();
            foreach(var item in documents)
            {
                Console.WriteLine(JsonSerializer.Serialize(item));
            }

因為 collection1 是標記為 Test1 的文件集合;但是 .Find(new BsonDocument()) 是查詢集合中的所有文件,因此獲取到 Test2。

但是 Test2 是不能轉為 Test1 的,因此,會導致程式報錯。

檢視所有文件

var documents = collection1.Find(new BsonDocument()).ToList();
var documents = await collection1.Find(new BsonDocument()).ToListAsync();

前面已經說過,如果集合中存在其它格式的文件,獲取全部文件時,因為 Test2 跟 Test1 沒任何關係,會導致 MongoDB.Driver 報錯。

如果文件數量比較大,要使用非同步的 ForEachAsync() 查詢,其原理是 回撥。

            List<Test1> tests = new List<Test1>();
            Action<Test1> action = item =>
            {
                tests.Add(item);
                Console.WriteLine(JsonSerializer.Serialize(item));
            };

            await collection1.Find(new BsonDocument()).ForEachAsync(action);

查詢結束

使用 Find() 以及後續函式查詢後,要結束查詢(延遲載入),可以使用 ToCursor() 函式結束,程式會立即開始查詢並將資料返回記憶體。

轉換查詢

使用 ToEnumerable() 可以使用 Linq 來查詢文件。

var list = collection1.Find(new BsonDocument()).ToCursor().ToEnumerable();

過濾器

前面我們查詢的時候都使用 .Find(new BsonDocument())BsonDocument 是過濾器物件,裡面儲存了過濾的規則,但是我們不能直接設定 new BsonDocument() 中的屬性,而是使用構建器FilterDefinitionBuilder物件,而此物件可以通過 MongoDB.Driver.Builders<TDocument>.Filter 建立 。

假設有以下資料集(文件):

5f8bdf88e63d14cb5f01dd85	小明	19
5f8bdf88e63d14cb5f01dd86	小紅	20
5f8bdf88e63d14cb5f01dd87	小張	16
5f8bdf88e63d14cb5f01dd88	小小	17
    
# -----插入資料的程式碼-----
    public class Test
    {
        public ObjectId _id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }

            var datas = new Test[]
            {
                new Test{ Name="小明",Age=19},
                new Test{ Name="小紅",Age=20},
                new Test{ Name="小張",Age=16},
                new Test{ Name="小小",Age=17}
            };

            collection.InsertMany(datas);

使用構建器:

FilterDefinition<Test> filter = Builders<Test>.Filter

查詢 Age 大於 18 的文件:

FilterDefinition<Test> filter = filterBuilder.Where(item => item.Age >= 18);

獲取結果:

Test[] documents = collection.Find(filter).ToEnumerable<Test>().ToArray();

過濾器還有 Gt()In()Lte() 等非 Linq 的函式,需要檢視文件學習。

Builders<TDocument>

Builders<TDocument> 除了能夠生成過濾構建器,還有其它幾種構建器:

		// 條件過濾
        public static FilterDefinitionBuilder<TDocument> Filter { get; }

		// 索引過濾
        public static IndexKeysDefinitionBuilder<TDocument> IndexKeys { get; }

		// 對映器,相當於使用 Linq 的 .Select() 查詢自己只需要的欄位
        public static ProjectionDefinitionBuilder<TDocument> Projection { get; }

		// 排序,建立排序規則,如工具年齡排序
        public static SortDefinitionBuilder<TDocument> Sort { get; }

		// 更新,更新某些欄位的值等
        public static UpdateDefinitionBuilder<TDocument> Update { get; }

詳細請參考 https://mongodb.github.io/mongo-csharp-driver/2.10/reference/driver/definitions/#projections

名稱對映

由於 MongoDB 區分欄位的大小寫,文件的欄位一般使用駝峰命名法,首字母小寫,而 C# 欄位屬性首字母是 大小開頭的,因此需要不同名稱對應起來。

可以使用 BsonElement 特性來設定對映的名稱。

class Person
{
    [BsonElement("fn")]
    public string FirstName { get; set; }

    [BsonElement("ln")]
    public string LastName { get; set; }
}

以上就是 MongoDB 的初入門知識,但是使用了 MongoDB 有什麼好處?可以參考阿里雲的這篇文章:https://developer.aliyun.com/article/64352

整理場景如下:

  • 儲存應用程式日誌。日誌結構化,查詢方便,可以匯出其它格式和二次利用。

  • 增加欄位不需要改動表結構,靈活變更。

  • 支援 json 格式匯入;類似 json 的資料結構;能夠很容易還原物件的屬性,一次性儲存資料;如果使用傳統資料庫,則需要建立多個表並設定主鍵外界關係。

  • 叢集。分散式叢集海量資料,容易擴充;故障轉移保證服務可用;

  • 解決分散式檔案儲存需求。

  • 索引方式靈活。

  • ... ...

相關文章