2021 iThome 鐵人賽 - DAY29 MongoDB 使用 C# Driver 操作

2021 iThome 鐵人賽 MongoDB披荊斬棘之路


DAY29 MongoDB 使用 C# Driver 操作

不知道有沒有人記得 DAY1 文章說本篇使用 .NET 平台,結果一直到第 29 天都還沒有寫到任何有關的東西,其實原本是想全程使用 .NET 來舉例,但考量到觀看者不一定使用 C#,還是乖乖回頭用 javascript 來說明。

會寫這一篇呢是想幫助過去的我自己,當時使用的語言是 C#,因專案要接觸 MongoDB,在語法上著實惱了一陣子,明明是很簡單的東西卻不習慣使用 Mongo C# Driver,導致額外花了不少時間在熟悉工具,要是有這一篇的話,肯定能快速上手(賣瓜?)

本篇文章會像是 Cheat sheet 性質,直接給予語法,一個速食的概念,實際應用在專案上,可以再重構得更漂亮些。另外本篇使用的語法都是強型別為主,畢竟使用 C# 就要發揮它的特性,如果使用字串代替欄位,雖然開發期很輕鬆快速,但很容易因為 typo 導致要抓 bug 抓很久,別問我為什麼知道,因為開發初期就吃了很多苦XD


以下的範例我們會使用下面這個 entity 來當作舉例

1
2
3
4
5
6
7
8
9
10
[BsonIgnoreExtraElements]
public class SampleClassEntity
{
[BsonId]
public string Id { get; set;}

public int IntField { get; set;}

public string StringField { get; set;}
}

[BsonIgnoreExtraElements] 是忽略沒有 mapping 到 C# 端的欄位

[BsonId] 是 Mongo C# driver 的 attribute,代表這個 property 是對應到 MongoDB _id 欄位。


連線 (Connection)

1
2
3
4
5
6
7
8
9
10
11
const string MongoAddress = "mongodb://localhost:27017";
const string HelloDatabase = "hellomongodb";
const string Collection = "sample";

var client = new MongoClient(MongoAddress);

public IMongoCollection<SampleClassEntity> GetCollection()
{
var _client = new MongoClient(MongoAddress);
return _client.GetDatabase(HelloDatabase).GetCollection<SampleClassEntity>(Collection);
}

建立 (Create)

1
await MongoHelper.GetCollection().InsertOneAsync(new SampleClassEntity());

一次建立多筆(bulk write)會在 Update 那邊一起使用,即 MongoDB 的 upsert

讀取 (Read)

  • 方法一
1
2
3
var builder = Builders<SampleClassEntity>.Filter;
var dbFilter = builder.Gt(x => x.IntField, 1)
& builder.Eq(x => x.StringField, "ComparedString");

首先我們建立一個 Filter 類型的 builder,接著拼湊出我們要的查詢條件。
以上面的範例是 IntField > 1StringField == ComparedString

1
2
3
4
var someCondition = true;

if (someCondition)
dbFilter &= builder.Gt(x => x.IntField, 2);

也會有一些狀況是特定條件下才會有的,上面是如果 someCondition == true,那就會需要多家這個過濾條件。

1
var result = await MongoHelper.GetCollection().Find(dbFilter).ToListAsync();

接著我們就可以將此 filter 放入 Find() 內當參數使用。
這邊的 await 與 ToListAsync 是非同步用法,如果是同步的呼叫那就不需要加上去。

  • 方法二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Find by cursor
var proj = Builders<SampleClassEntity>.Projection
.Include(x => x.Id)
.Include(x => x.IntField);

using var cursor = await MongoHelper.GetCollection()
.WithReadPreference(ReadPreference.SecondaryPreferred)
.FindAsync(dbFilter, new FindOptions<SampleClassEntity>
{
Sort = Builders<SampleClassEntity>.Sort.Descending(x => x.Id),
Skip = 1,
Limit = 2,
Projection = proj
});

var result = cursor.ToEnumerable().ToArray();

這邊除了使用 cursor 方式之外多了一些東西。

  • Projection
    這就相當於要取出甚麼欄位,分別使用 IncludeExclude
  • WithReadPreference(ReadPreference.SecondaryPreferred)
    Read傾向使用哪個節點,使用時可以查看 ReadPreference enum 定義了那些
  • FindOptions
    查詢的額外設定,有排序、跳過、數量限制以及 Project 設定

更新 (Update)

更新分為 Upsert 與 Replace 兩個項目來講,但本質上不會差太多的,一個是更新(設定)特定欄位,另一個是整個物件取代。
這邊的範例是直接用 BulkWrite 方式,批量進行修改了。

  • Upsert
1
2
3
4
5
6
7
8
9
10
11
12
13
var src = Array.Empty<SampleClassEntity>();
var bulks = src.Select(entity =>
{
return new UpdateOneModel<SampleClassEntity>
(
Builders<SampleClassEntity>.Filter.Eq(x => x.Id, ""),
Builders<SampleClassEntity>.Update
.Inc(x => x.IntField, 1)
.Set(x => x.StringField, "2")
) {IsUpsert = true};
}).ToArray();

await MongoHelper.GetCollection().BulkWriteAsync(bulks, new BulkWriteOptions{IsOrdered = false});

Update 的方式須特別指定要更新的欄位,這邊舉了
Inc 即 Increment,增加數量的意思
Set 更新成新值
SetOnInsert 是在第一次寫入時才會使用該值,後面任何 Update 都不會修改
IsOrdered 整批量個更新不需要按順序執行(之前文章有提到)

  • Replace
1
2
3
4
5
6
7
8
9
10
11
var src = Array.Empty<SampleClassEntity>();
var bulks = src.Select(x =>
{
var replace = new ReplaceOneModel<SampleClassEntity>(
Builders<SampleClassEntity>.Filter.Eq(m => m.Id, x.Id), x)
{IsUpsert = true};
return replace;

}).ToArray();

await MongoHelper.GetCollection().BulkWriteAsync(bulks);

Replace 相對單純一些,只需要 Filter 找到特定的文件,進行整個內容取代。

刪除 (Delete)

1
2
3
var filter = Builders<SampleClassEntity>.Filter.In(x => x.Id, new[] {"ToDeleteId"});

await MongoHelper.GetCollection().DeleteManyAsync(filter);

刪除部分是直接用 DeleteMany,可以按需求使用 DeleteOne

最後還是要強調,這只是語法的演示,實務上需要再調整寫法與效能。

  • 作者: MingYi Chou
  • 版權聲明: 轉載不用問,但請註明出處!本網誌均採用 BY-NC-SA 許可協議。