昨天搭建完畢 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 有很多實用工具: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 中的表達式,基礎了 IEnumerable或 IEnumerable<T> 接口,因此驅動沒有 Where、Select 這種表達式的查詢方法。
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 的數據結構;能夠很容易還原對象的屬性,一次性存儲數據;如果使用傳統數據庫,則需要建立多個表並設置主鍵外界關系。
-
集群。分布式集群海量數據,容易拓展;故障轉移保證服務可用;
-
解決分布式文件存儲需求。
-
索引方式靈活。
-
... ...
