建立 .NET + MongoDB 本機開發環境

MongoDB 近幾年來發展的很穩定,也是我心目中 NoSQL 選擇方案之一,對於 .NET 的開發者而言,開發體驗也算是不錯,在這裡我會簡單的分享怎麼建立  .NET + MongoDB 開發環境以及簡單的 CRUD 操作

開發環境

  • Windows 11
  • Rider 2023.2
  • .NET 7
  • MongoDB.Driver 2.21.0

 

利用 Docker 建立 MongoDB 開發環境

通過 Docker 搭建 MongoDB 的開發環境,很容易在本機搭建起 Local MongoDB Server,作為測試替身也是非常的合適的 mongo - Official Image | Docker Hub

有了 MongoDB 再搭配一套 Client,mongo-express 是一套 Web UI 的 MongoDB 管理工具,搭配他用來除錯是一個不錯的選擇,他也提供了 Docker mongo-express - Official Image | Docker Hub

將以下內容另存 docker-compose.yml

version: "3.8"

services:
  mongo:
    image: mongo
    container_name: mongo_test
    ports:
      - 27017:27017

  mongo-express:
    image: mongo-express
    container_name: mongo_express_test
    ports:
      - 8081:8081

 

用 Shell 啟動它

docker-compose.yml up -d

 

MongoDB Client

mongo-express

訪問 http://localhost:8081,就可以進入 Web UI 管理介面

 

Jetbrains

Jetbrains 系列的  IDE 本身就內建連接 MongoDB 的 Driver,下圖是我用 Rider 連接 MongoDB 的演示

 

MongoDB Compass

官方提供的 MongoDB Compass

選擇自己所需的平台下載

MongoDB Compass Download (GUI) | MongoDB

 

我選擇用 winget

winget install MongoDB.Compass.Full

 

打開它,連接 MongoDB 後,可以觀看效能分析,真的讚

 

上述 MongoDB Client 挑一個喜歡的就可以

使用 MongoDB.Driver 訪問 MongoDB Server

開啟一個新的 .NET 7 的 Test 專案

dotnet new mstest --language "C#" --framework "net7.0" -o Lab.MongoDB.CRUD.TestProject

安裝套件

dotnet add package MongoDB.Driver --version 2.21.0

 

連接 MongoDB

// MongoDB 連線字串
var connectionString = "mongodb://localhost:27017";

// 產生 MongoClient 物件
var mongoClient = new MongoClient(connectionString);

// 取得 MongoServer 物件
var mongoServer = mongoClient.GetServer();

// 取得 MongoDatabase 物件
var mongoDatabase = mongoServer.GetDatabase("test");

// 取得 Collection
mongoCollection = mongoDatabase.GetCollection<Product>("Products");

 

更多的連接設定,請參考

https://www.mongodb.com/docs/drivers/csharp/current/fundamentals/connection/

官方的 MongoDB.Driver 直接提供  Strong Object 對應 MongoDB Document 的機制,搭配 LINQ 查詢資料,開發體驗真的不賴。

定義一個 Product 物件

public record Product
{
    public string Id { get; init; }

    public string Name { get; init; }

    public decimal Price { get; init; }

    public string Remark { get; init; }
}

 

更多的定義,請參考

https://www.mongodb.com/docs/drivers/csharp/current/fundamentals/data-formats/poco/

 

開始之前,還需要準備測試環境、還原測試資料

private static MongoDbContainer MongoDbContainer;
private static MongoClient MongoClient;
private readonly string TestData = "出發吧,讓我們航向偉大的航道";

[ClassInitialize]
public static async Task ClassInitialize(TestContext context)
{
    var mongoClientSettings = new MongoClientSettings()
    {
        Server = new MongoServerAddress("localhost", 27017),
    };

    MongoClient = new MongoClient(mongoClientSettings);
}

[TestCleanup]
public void TestCleanup()
{
    //復原資料
    var mongoCollection = MongoClient.GetDatabase("example").GetCollection<Product>("product");
    var filter = Builders<Product>.Filter
        .Eq(r => r.Remark, this.TestData);
    var data = mongoCollection.DeleteMany(filter);
}

 

除了 Strong Object 之外,也可以用 BsonElement,用起來就有點像 ADO.NET DataColumn、DataRow,詳細用法請參考

Work with BSON — C#/.NET (mongodb.com)

 

新增資料

[TestMethod]
public async Task 新增一筆資料()
{
    var mongoCollection = MongoClient.GetDatabase("example").GetCollection<Product>("product");
    var expected = new Product
    {
        Id = "1",
        Name = "TV",
        Price = 33.11m,
        Remark = this.TestData
    };

    //新增一筆資料
    await mongoCollection.InsertOneAsync(expected);

    //驗證
    var actual = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1");
    Assert.AreEqual(expected, actual);
}

 

更新資料

  • 先用 Builders<Product>.Filter 定義更新甚麼資料
  • 再用 Builders<Product>.Update 指定要更新的欄位
  • 最後用 MongoCollection.UpdateOneAsync 寫入資料

完整代碼如下:

[TestMethod]
public async Task 更新一筆資料()
{
    var mongoCollection = MongoClient.GetDatabase("example").GetCollection<Product>("product");
    var expected = new Product
    {
        Id = "1",
        Name = "TV",
        Price = 33.11m,
        Remark = this.TestData
    };

    //產生資料
    var products = this.GenerateProducts();
    await mongoCollection.InsertManyAsync(products);

    var filter = Builders<Product>.Filter
        .Eq(restaurant => restaurant.Id, "1");

    var update = Builders<Product>.Update
        .Set(restaurant => restaurant.Name, "TV");

    //更新資料
    await mongoCollection.UpdateOneAsync(filter, update);

    //驗證
    var actual = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1");

    Assert.AreEqual(expected, actual);
}

 

異動、刪除、查詢資料前的準備資料,完整代碼如下:

private List<Product> GenerateProducts()
{
    var products = new List<Product>()
    {
        new()
        {
            Id = "1",
            Name = "Air jordan 11",
            Price = 33.11m,
            Remark = this.TestData
        },
        new()
        {
            Id = "2",
            Name = "Air jordan 12",
            Price = 33.12m,
            Remark = this.TestData
        },
        new()
        {
            Id = "3",
            Name = "Air jordan 13",
            Price = 33.13m,
            Remark = this.TestData
        }
    };
    return products;
}

 

刪除資料

  • 先用 Builders<Product>.Filter 定義更新甚麼資料
  • 最後用 MongoCollection.DeleteOneAsync 刪除資料

完整代碼如下:

[TestMethod]
public async Task 刪除資料()
{
    var mongoCollection = MongoClient.GetDatabase("example").GetCollection<Product>("product");

    //產生資料
    var products = this.GenerateProducts();
    await mongoCollection.InsertManyAsync(products);

    var filter = Builders<Product>.Filter
        .Eq(restaurant => restaurant.Id, "1");
    
    //更新資料
    await mongoCollection.DeleteOneAsync(filter);

    //驗證
    var actual = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1");

    Assert.AreEqual(null, actual);
}

 

查詢資料

有兩種查詢方式,分別是 Builder<T>.Filter、LINQ

Builder<T>.Filter

var filter = Builders<Product>.Filter.Eq(x => x.Id, "1");
var find = await mongoCollection.FindAsync(filter);
var data1 = await find.FirstOrDefaultAsync();

 

LINQ 用法,先用 AsQueryable

var data2 = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1");

 

完整代碼如下:

[TestMethod]
public async Task 查詢()
{
    var mongoCollection = MongoClient.GetDatabase("example").GetCollection<Product>("product");
    var expected = new Product
    {
        Id = "1",
        Name = "Air jordan 11",
        Price = 33.11m,
        Remark = this.TestData
    };

    //產生資料
    var products = this.GenerateProducts();
    await mongoCollection.InsertManyAsync(products);

    //查詢1
    var filter = Builders<Product>.Filter.Eq(x => x.Id, "1");
    var find = await mongoCollection.FindAsync(filter);
    var data1 = await find.FirstOrDefaultAsync();

    //驗證
    Assert.AreEqual(expected, data1);

    //查詢2
    var data2 = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1");

    //驗證
    Assert.AreEqual(expected, data2);
}

 

更多的範例

https://www.mongodb.com/docs/drivers/csharp/current/usage-examples/

https://www.mongodb.com/docs/drivers/csharp/current/fundamentals/crud/

Builder<T>.Filter 對應 MongoDB 的操作

https://www.mongodb.com/docs/drivers/csharp/current/fundamentals/crud/read-operations/specify-query/

 

停止 docker-compose,套用 TestContainer,套用上篇學到的東西 

[ClassInitialize]
public static async Task ClassInitialize(TestContext context)
{
    MongoDbContainer = new MongoDbBuilder()
        .WithPortBinding(27017, true)
        .Build();
    await MongoDbContainer.StartAsync();
    var mongoClientSettings = MongoClientSettings.FromConnectionString(MongoDbContainer.GetConnectionString());

    MongoClient = new MongoClient(mongoClientSettings);
}

 

範例位置

sample.dotblog/MongoDB/Lab.MongoDB.CRUD at 645a13d9940a583c3ecce9c4cb0aefb25461ff52 · yaochangyu/sample.dotblog (github.com)

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo