F#でMongoDBクライアント操作実験 - クライアントの起動からドキュメント操作まで
ここではF#でMongoDBのクライアントを起動し、MongoDBのデータベースに接続して基本的なドキュメント操作(挿入、検索、更新、削除)を実験的にやってみます。MongoDBの公式ドライバはC#向けとなっていますので、F#はサポート外でしょうが、同じ.NET環境でのプログラミングが可能なF#であえて実験してみようというわけです。
F#について
MongoDBについて
MongoDBはリレーショナルデータベースとは異なる形式のデータを扱うNoSQLデータベースのひとつです。データベースにはコレクションと呼ばれるデータの保存場所が作成され、そこにJSONのような階層化された構造を持つデータであるドキュメントが保存されます。そして保存されたデータに対しては検索などの操作ができます。
ここではPCにインストール可能なMongoDB Community Editionを利用しますが、クラウドサービスのMongoDB Atlasも提供されています。
実行環境について
以下がインストール済みで、F#のプログラムを実行する前にMongoDBサーバのインストールとmongodコマンドによるサーバの起動は済んでいるものとします。
- MongoDB Community Edititon 8.0 / インストール(マニュアル)
- .NET SDK 9.0 RC 2 / インストール(マニュアル)
- MongoDB C# ドライバー 2.29.0 / マニュアル
そしてF#の実行環境は以下の2つがあります。MongoDBサーバへのアクセスはどちらでも可能です。
- 対話型実行環境(REPL)
- dotnetコマンドで生成するビルド環境
F#の対話型実行環境(REPL)
dotnet fsi
コマンドを実行すると対話型実行環境(REPL)が起動されます。ここでソースコードを入力し、末尾に(コメント//
の場合は改行してから);;
をつけてEnter
キーを押すと記述した分のソースコードが実行されます。複数行をまとめて入力してから実行することもできます。
> dotnet fsi
Microsoft (R) F# インタラクティブ バージョン F# 9.0 のための 12.9.100.0
Copyright (C) Microsoft Corporation. All rights reserved.
ヘルプを表示するには次を入力してください: #help;;
> printfn "Hello, F#!";; (ソースコードを入力してEnter)
Hello, F#! (出力結果)
val it: unit = () (実行したプログラムからの戻り値)
>
また、ソースコードをファイル名.fsx
に保存してdotnet fsi ファイル名.fsx
のように実行することもできます。
dotnetコマンドで生成するビルド環境
dotnet
コマンドによりビルド環境を生成し、そこでプログラムのコンパイルと実行ができます。
> dotnet new console -lang f# -o fsmongo (ビルド環境をfsmongoディレクトリ内に生成)
> cd fsmongo (ビルド環境に移動)
> dotnet add package MongoDB.Driver (ビルド環境にMongoDBドライバを追加)
..... Program.fs(ソースコードファイル)を編集 .....
> dotnet run (プログラム実行)
クライアントの起動と接続
まずはMongoDBクライアントの起動と接続をしてみます。そのためにはMongoDBClient
のインスタンスを生成します。MongoDBの接続先はデフォルトとします。ただし、これから以下すべてにおいて非同期処理は扱わないものとします。
#if INTERACTIVE
#r "nuget: MongoDB.Driver" // 対話型実行環境で実行される部分(MongoDBドライバの追加)
#endif
open MongoDB.Driver
let client = MongoClient()
データベース名一覧の取得
現時点で作成済みであるデータベース名の一覧を取得してみます。
client.ListDatabaseNames().ToList() |> printfn "%A"
// seq ["admin"; "config"; "local"] (これらはインストール直後に作成済み)
データベースの取得
ここで使用するデータベースを取得します。この時点でデータベースが存在していればすぐアクセス可能ですが、まだ存在していない場合はコレクションとドキュメントを追加するときにデータベースも作成されます。
let db = client.GetDatabase("fsmongo")
コレクションの取得
コレクションを取得するときはクラスやレコード型でドキュメントの形式を指定します。この時点でコレクションが作成済みであればすぐアクセス可能ですが、そうでなければドキュメントがコレクションに追加されるときに作成されます。
open MongoDB.Bson
open MongoDB.Bson.Serialization.Attributes
// ドキュメントの形式(ここではレコード型)
type Bookmark = {
[<BsonId>] id: ObjectId
title: string
url: string
tags: string seq
}
// コレクションの取得
let col = db.GetCollection<Bookmark>("bookmark")
ドキュメントの追加
取得したコレクションにドキュメントを保存してみます。保存は複数一括か、あるいは1件ずつ実行できます。コレクションに保存されたデータの件数はEstimatedDocumentCountメソッドで実行できます。
// コレクションに追加するドキュメント
let bm = [
{
id = ObjectId.GenerateNewId() // id生成
title = "MongoDB"
url = "https://www.mongodb.com"
tags = seq {"MongoDB"; "Database"}
}
{
id = ObjectId.GenerateNewId()
title = "F# ドキュメント"
url = "https://learn.microsoft.com/ja-jp/dotnet/fsharp"
tags = seq {"Fsharp"; "Programming"}
}
{
id = ObjectId.GenerateNewId()
title = "pg_walker"
url = "https://zenn.dev/pgwalker"
tags = seq {"Fsharp"; "Zig"; "Programming"}
}
]
// 複数のドキュメントを一括で追加
col.InsertMany(bm)
// ドキュメントを1件ずつ追加するとき
// col.InsertOne(bm[0])
// col.InsertOne(bm[1])
// col.InsertOne(bm[2])
// コレクションに保存されているドキュメントの数
col.EstimatedDocumentCount() |> printfn "EstimatedDocumentCount: %A"
idを自動生成するには
ドキュメントのidを自動的に生成する場合は、その項目にIdGeneratorを設定します。その種類はMongoDB.Bson.Serialization.IdGeneratorsに定義されています。このときドキュメントの各項目をmutable
とすることがポイントです。
open MongoDB.Bson.Serialization.IdGenerators
type Bookmark = {
[<BsonId(IdGenerator = typeof<StringObjectIdGenerator>)>]
mutable id: string
mutable title: string
mutable url: string
mutable tags: string seq
}
// コレクションに追加するドキュメント
let bm = [
{
id = "" // コレクション追加時に値が設定される
title = "MongoDB"
url = "https://www.mongodb.com"
tags = seq {"MongoDB"; "Database"}
}
{
id = ""
title = "F# ドキュメント"
url = "https://learn.microsoft.com/ja-jp/dotnet/fsharp"
tags = seq {"Fsharp"; "Programming"}
}
{
id = ""
title = "pg_walker"
url = "https://zenn.dev/pg_walker"
tags = seq {"Fsharp"; "Zig"; "Programming"}
}
]
データベースとコレクションの一覧
ドキュメントを追加した後に取得したデータベースやコレクションが作成されたかどうかを確認しておきます。以下の結果がtrue
であれば作成済みです。
// データベース'fsmongo'が作成されたか
client.ListDatabaseNames().ToEnumerable()
|> Seq.contains "fsmongo"
|> printfn "Database 'fsmongo': %A" // true
// コレクション'bookmark'が作成されたか
db.ListCollectionNames().ToEnumerable()
|> Seq.contains "bookmark"
|> printfn "Collection 'bookmark': %A" // true
ドキュメントの検索
コレクションにドキュメントを追加できたら、それらを条件[1]を変えながら検索してみます。
全件検索
全件検索の場合は条件なしのEmpty
で行います。このときToEnumerable()
によって検索結果をシーケンス(seq)で扱えるようにしてみます。
// 全件検索
let filter = Builders<Bookmark>.Filter
col.Find(filter.Empty).ToEnumerable()
|> Seq.iter (fun d -> printfn "%A" d)
{ id = 670b771ca26fb883f1ee1546
title = "MongoDB"
url = "https://www.mongodb.com"
tags = seq ["MongoDB"; "Database"] }
{ id = 670b771ca26fb883f1ee1547
title = "F# ドキュメント"
url = "https://learn.microsoft.com/ja-jp/dotnet/fsharp"
tags = seq ["Fsharp"; "Programming"] }
{ id = 670b771ca26fb883f1ee1548
title = "pg_walker"
url = "https://zenn.dev/pgwalker"
tags = seq ["Fsharp"; "Zig"; "Programming"] }
設定した条件により検索されるデータの件数はCountDocumentsメソッドで確認できます。
col.CountDocuments(filter.Empty) |> printfn "CountDocuments: %A"
項目指定
検索する項目を指定するときはラベル(もしくはプロパティ)と検索条件をfilter.Eq("title", "MongoDB")
のように設定します。項目が配列の場合も同じです。また、文字列の項目であれば正規表現による比較もできます。
// 項目をレコードのラベルで指定するとき(項目をラベルや文字列で指定)
col.Find(filter.Eq("title", "MongoDB")).First() |> printfn "title = MongoDB\n%A" // 項目が文字列の配列のとき、その配列の要素で指定
col.Find(filter.Eq("tags", "Programming")).ToEnumerable() |> printfn "Pragramming tag\n%A"
// 項目が文字列など単体の場合はプロパティでの比較も可
col.Find(filter.Eq(_.title, "MongoDB")).First() |> printfn "_.title = MongoDB\n%A"
// 正規表現による検索
open System.Text.RegularExpressions
col.Find(filter.Regex("url", Regex "dotnet")).First() |> printfn "url ~= dotnet\n%A"
ドキュメントの更新
コレクションに追加されたドキュメントのうち、特定のidを持つものについて一部の項目を更新してみます。
// 更新するドキュメントを検索する条件
// col.Find(filter.Eq("id", bm[0].id)).First()
// 更新するデータ(レコード型の一部を更新)
let bm_new = {
bm[0] with
title = "MongoDB Developer"
url = "https://www.mongodb.com/developer/"
}
// ドキュメントの更新(ModifiedCountは更新した件数)
col.ReplaceOne(filter.Eq("id", bm[0].id), bm_new).ModifiedCount |> printfn "ModifiedCount: %A"
// 更新されたドキュメントの検索
col.Find(filter.Regex("title", Regex "Developer")).First() |> printfn "title ~= Developer\n%A"
ドキュメントの削除
検索条件に適合するドキュメントの削除をしてみます。この後も必要なドキュメントを削除してしまわないように検索条件の設定は慎重に確認してください。
// 特定のドキュメントを削除(DeletedCountは削除した件数)
col.DeleteOne(filter.Eq("title", "pg_walker")).DeletedCount |> printfn "DeletedCount: %A"
// コレクション内のすべてのドキュメントを削除
col.DeleteMany(filter.Empty).DeletedCount |> printfn "DeletedCount: %A"
// コレクション内にあるドキュメントの件数
col.EstimatedDocumentCount() |> printfn "EstimatedDocumentCount: %A"
コレクションの削除
コレクションの削除はデフォルトで実行する限り戻り値がなく(void
)、存在しないコレクションの名称を指定してもエラーになりません。
db.DropCollection("bookmark")
データベースの削除
データベースの削除もコレクションと同様、デフォルトで実行する限り戻り値がなく(void
)、存在しないデータベースの名称を指定してもエラーになりません。
client.DropDatabase("fsmongo")
関連Webサイト
-
MongoDB
- MongoDB ドキュメント(日本語)
- MongoDB とは?(日本語)
- MongoDB で開発を開始(日本語) - 言語や環境ごとにドキュメントを選択
- MongoDB C# ドライバー(日本語)
- MongoDB Developer Center - 言語や環境ごとにドキュメントを選択
- C# | MongoDB
-
F#
- F# ドキュメント(日本語)
- F# 言語リファレンス(日本語)
- F# Core Library Documentation - APIドキュメント
- F# Cheat Sheet - 文法の短いリファレンス集
- .NET API browser(日本語) - 画面上のUIで.NETのバージョンを設定
Discussion