iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Software Development

MongoDB披荊斬棘之路系列 第 29

DAY29 MongoDB 使用 C# Driver 操作

DAY29 MongoDB 使用 C# Driver 操作

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

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

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


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

[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)

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)

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

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

讀取 (Read)

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

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

var someCondition = true;

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

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

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

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

  • 方法二
// 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
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
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)

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

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

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

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


本系列文章會同步發表於我個人的部落格 Pie Note


上一篇
DAY28 MongoDB Atlas 付費監控內容
下一篇
DAY30 MongoDB 使用經驗分享 & 完賽
系列文
MongoDB披荊斬棘之路30

尚未有邦友留言

立即登入留言