CloudFireStoreサンプルで学ぶ、クラウドリアルタイムデータベース【ベース編】

Firestoreは、Google Cloud Platform(GCP)上で提供されている、クラウドベースのNoSQLデータベースサービスです。Firestoreは、アプリケーションのデータを保存、同期、クエリ、表示するための強力なツールを提供します。主にウェブアプリケーションやモバイルアプリケーションのバックエンドとして使用されます

FireStore

特徴

Firestoreの特徴は以下の通りです:

  1. NoSQLデータベース: Firestoreは、伝統的なテーブル型のリレーショナルデータベースではなく、ドキュメント指向のNoSQLデータベースです。データは階層構造のコレクションとドキュメントで管理され、各ドキュメント内にはフィールドとその値が含まれます。
  2. リアルタイム同期: Firestoreはリアルタイムデータ同期をサポートしており、データベース内の変更が即座にクライアントアプリケーションに反映されます。これにより、リアルタイムな更新やチャットアプリケーションなどで便利に使用できます。
  3. スケーラビリティ: Firestoreは自動的にスケーリングされ、大規模なアプリケーションでも高いパフォーマンスを維持できるように設計されています。また、データベースの読み書き操作が高速であることも特徴です。
  4. クエリと索引: Firestoreは強力なクエリ機能を提供し、データベース内のデータを柔軟にフィルタリング、ソート、絞り込むことができます。また、インデックスが自動的に作成されるため、複雑なクエリも高速に実行できます。
  5. セキュリティ: Firestoreはセキュリティにも配慮されており、アクセスコントロールを設定してデータのプライバシーやセキュリティを確保することができます。

Firestoreはウェブアプリケーションやモバイルアプリケーションのデータベースとして広く利用されており、リアルタイムなデータ同期やスケーラビリティが求められる場面で特に有用です。Firebaseというプラットフォームの一部として提供されており、Firebaseの他の機能と連携させることで、簡単にアプリケーション全体の開発とデプロイメントを管理することができます。

データベースの構成イメージ

コントールパネル

実行中画面

ドキュメント

登録

プロジェクトの設定で秘密鍵を生成、ファイルに保存します

コンソールアプリ

基本的は動作について学ぶには小さなサンプルから始めるのが常套手段です
エラー処理や見栄えにこだわらず、できれば数行程度のコードで完結するものの作成を心がけます

次に少しずつデータを増やしたり、削除、更新処理を増やしたり基本検証を進めていきます

コツはいきなり大きな完成形を目指さないこと
大きな形のコードに出会ったもネガティブにならないことだと思います

コード

たくさんの行数があると感じる方もおられるでしょうが、数行から始めています
全ての過程を表現するのが不可能なため、ある程度まとまったコードを掲載しています
(Gitでのコミット履歴を確認するのが一番ですね。Gitは公開していません)

データベースにアクセスするためのキーのPathを実行フォルダーに保存しておく必要があります

アクセスキーファイル名

practice-395800-firebase-adminsdk-o49jw-48c6c0b78a.json

保存場所

Program.cs

// ダウンロードしたサービスアカウントのアクセスキーの
// 保存パスを環境変数に設定する
using Google.Cloud.Firestore;

FirestoreDb db = CreateDB();
await SetBookDB(db);

//await Book(db);

static async Task SetBookDB(FirestoreDb db)
{
    var bookRef = db.Collection("Books");

    var book = new Dictionary<string, object>()
    {
        ["Name"] = "老人と海",
        ["Author"] = "ヘミングウェイ",
        ["Price"] = "700",
    };

    await bookRef.Document("book1").SetAsync(book);

    await bookRef.Document("book2").SetAsync
    (
        new Dictionary<string, object>()
        {
            ["Name"] = "吾輩は猫である",
            ["Author"] = "夏目漱石",
            ["Price"] = "500",
        }
    );

    await bookRef.Document("book3").SetAsync
    (
        new Dictionary<string, object>()
        {
            ["Name"] = "雪国",
            ["Author"] = "川端康成",
            ["Price"] = "600",
        }
    );

    Console.WriteLine("本のデータベースに1冊の本を追加しました");



    await SetCollection(db);
    await GetAllCollection(db);

    await DeleteBook(bookRef, "book3");

}

static async Task DeleteBook(CollectionReference bookRef, string document)
{
    // ドキュメントを削除
    await bookRef
        .Document(document)
        .DeleteAsync();
}
static async Task GetAllCollection(FirestoreDb db)
{
    // コレクション を指定して 1 件取得
    var documentSnapshot = await db.Collection("Books")
        .GetSnapshotAsync();

    foreach (var item in documentSnapshot)
    {
        string name = item.GetValue<string>("Name");
        string author = item.GetValue<string>("Author");
        string price = item.GetValue<string>("Price");
        Console.WriteLine($"{name} {author} ({price}円)");
    }


}

static async Task Book(FirestoreDb db)
{
    // books コレクションにドキュメント追加
    var book1Ref = await db.Collection("books")
        .AddAsync(new Dictionary<string, object>()
        {
            ["title"] = "かぐや様は告らせたい",
            ["price"] = 500,
        });

    // books コレクションに ID 指定でドキュメント追加
    var book2Ref = db.Collection("books").Document("bokuben");
    await book2Ref.CreateAsync(new Dictionary<string, object>()
    {
        ["title"] = "ぼくたちは勉強ができない",
        ["price"] = 450,
    });

    // books を価格順にソートして取得
    var querySnapshot = await db.Collection("books")
        .OrderBy("price")
        .GetSnapshotAsync();
    foreach (var doc in querySnapshot.Documents)
    {
        Console.WriteLine($"{doc.GetValue<string>("title")}({doc.GetValue<int>("price")}円)");
    }

    // ドキュメントを更新
    await db.Collection("books")
        .Document("bokuben")
        .SetAsync(new Dictionary<string, object>()
        {
            ["price"] = 420,
        }, SetOptions.MergeAll);

    // ID を指定して 1 件取得
    var documentSnapshot = await db.Collection("books")
        .Document("bokuben")
        .GetSnapshotAsync();
    Console.WriteLine($"{documentSnapshot.GetValue<string>("title")}({documentSnapshot.GetValue<int>("price")}円)");

    // ドキュメントを削除
    await db.Collection("books")
        .Document(book1Ref.Id)
        .DeleteAsync();
    await db.Collection("books")
        .Document("bokuben")
        .DeleteAsync();


    /*
    Console.WriteLine("Created Cloud Firestore client with project ID: {0}", projectId);

    string documentName = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");
    DocumentReference docRef = db.Collection("users").Document(documentName);
    Dictionary<string, object> user = new Dictionary<string, object>
    {
        { "AccessDate", Timestamp.GetCurrentTimestamp() },
        { "IdNumber", 6548 },
        { "Name", "C#" }
    };

    await docRef.SetAsync(user);

    //user = new Dictionary<string, object>
    //{
    //    { "First", "Alan" },
    //    { "Middle", "Mathison" },
    //    { "Last", "Turing" },
    //    { "Born", 1912 }
    //};
    //await docRef.SetAsync(user);


    CollectionReference usersRef = db.Collection("users");
    QuerySnapshot snapshot = await usersRef.GetSnapshotAsync();

    List<FirestoreData> list = new List<FirestoreData>();
    foreach (DocumentSnapshot document in snapshot.Documents)
    {
        FirestoreData data = new FirestoreData
        {
            Id = document.Id,
            AccessDate = document.GetValue<Timestamp>("AccessDate").ToDateTime(),
            IdNumber = document.GetValue<double>("IdNumber"),
            Name = document.GetValue<string>("Name")

        };

        list.Add(data);
    }

    foreach (var item in list)
    {
        Console.WriteLine(item.AccessDate);
        Console.WriteLine(item.Id);
        Console.WriteLine(item.IdNumber);
        Console.WriteLine(item.Name);

    }

    */
}

static async Task SetCollection(FirestoreDb db)
{
    // コレクション を指定して 1 件取得
    var documentSnapshot = await db.Collection("Books")
        .Document("book1")
        .GetSnapshotAsync();


    string name = documentSnapshot.GetValue<string>("Name");
    string author = documentSnapshot.GetValue<string>("Author");
    string price = documentSnapshot.GetValue<string>("Price");
    Console.WriteLine($"{name} {author} ({price}円)");
}

static FirestoreDb CreateDB()
{
    string accessKeyPath = "/Users/konishihideaki/Key/practice-395800-firebase-adminsdk-o49jw-48c6c0b78a.json"; // "/Users/konishihideaki/Key"

    Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", accessKeyPath);

    string projectId = "practice-395800";
    FirestoreDb db = FirestoreDb.Create(projectId);
    return db;
}

WindowsFormsアプリ

次にビジュアル面で少し強化します
全く新たしいものを作っても構いません

今回は都合上、現在ローカルデータベースとなっているサンプルをクラウドDBへ仕様変更したものを掲載します

特徴

ローカルデータベース型の書籍管理アプリがベースになっています
これを元にネットワーク側(クラウドデータベース)でデータを管理するように仕様を更新しています

データの追加、削除、更新作業が行えます
データはリアルタイムでサーバを更新し、同時に他のクライアントにも配信されます(されるはずです)

データベースに変更があった場合、リアルタイムでビューが更新されるようにしています
(当初、更新ボタンを備えていましたが、APIがコールバックを備えていることからこのような仕様にしました

複数のユーザーでも動作すると思われますが、そこまでのテストはしておりません

学習目的のため、可能な限りコードの改変を少なくするよう心がけましたが、多少のエラー処理の追加やUIのテコ入れによって、変更箇所が膨れてしまいました

オリジナルと比較して学習に繋げて欲しいのですが、難易度が上がったかもしれません
コメントも多く入れておきましたので、頑張って分析してみてください

動作画面

デザイン画面

コード

データベースにアクセスするためのキーのPathを実行フォルダーに保存しておく必要があります

Form1.Designer.cs

namespace BookManager
{
    partial class Form1
    {
        /// <summary>
        /// 必要なデザイナー変数です。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 使用中のリソースをすべてクリーンアップします。
        /// </summary>
        /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows フォーム デザイナーで生成されたコード

        /// <summary>
        /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディターで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
            this.bookDataGrid = new System.Windows.Forms.DataGridView();
            this.書名DataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.著者DataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.値段DataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.bookDataTableBindingSource = new System.Windows.Forms.BindingSource(this.components);
            this.bookDataSet = new BookManager.BookDataSet();
            this.label1 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.label3 = new System.Windows.Forms.Label();
            this.bookName = new System.Windows.Forms.TextBox();
            this.author = new System.Windows.Forms.TextBox();
            this.price = new System.Windows.Forms.MaskedTextBox();
            this.addButton = new System.Windows.Forms.Button();
            this.removeButton = new System.Windows.Forms.Button();
            this.dbReadLabel = new System.Windows.Forms.Label();
            ((System.ComponentModel.ISupportInitialize)(this.bookDataGrid)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.bookDataTableBindingSource)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.bookDataSet)).BeginInit();
            this.SuspendLayout();
            // 
            // bookDataGrid
            // 
            this.bookDataGrid.AllowUserToAddRows = false;
            this.bookDataGrid.AllowUserToDeleteRows = false;
            this.bookDataGrid.AllowUserToResizeColumns = false;
            this.bookDataGrid.AllowUserToResizeRows = false;
            this.bookDataGrid.AutoGenerateColumns = false;
            this.bookDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
            this.bookDataGrid.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single;
            dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
            dataGridViewCellStyle1.BackColor = System.Drawing.Color.DeepSkyBlue;
            dataGridViewCellStyle1.Font = new System.Drawing.Font("MS UI Gothic", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128)));
            dataGridViewCellStyle1.ForeColor = System.Drawing.Color.White;
            dataGridViewCellStyle1.SelectionBackColor = System.Drawing.Color.LightSkyBlue;
            dataGridViewCellStyle1.SelectionForeColor = System.Drawing.Color.Black;
            dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
            this.bookDataGrid.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
            this.bookDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.bookDataGrid.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
            this.書名DataGridViewTextBoxColumn,
            this.著者DataGridViewTextBoxColumn,
            this.値段DataGridViewTextBoxColumn});
            this.bookDataGrid.DataSource = this.bookDataTableBindingSource;
            dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
            dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window;
            dataGridViewCellStyle2.Font = new System.Drawing.Font("MS UI Gothic", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128)));
            dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText;
            dataGridViewCellStyle2.SelectionBackColor = System.Drawing.Color.LightCyan;
            dataGridViewCellStyle2.SelectionForeColor = System.Drawing.Color.Black;
            dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
            this.bookDataGrid.DefaultCellStyle = dataGridViewCellStyle2;
            this.bookDataGrid.EnableHeadersVisualStyles = false;
            this.bookDataGrid.Location = new System.Drawing.Point(27, 26);
            this.bookDataGrid.MultiSelect = false;
            this.bookDataGrid.Name = "bookDataGrid";
            this.bookDataGrid.ReadOnly = true;
            dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
            dataGridViewCellStyle3.BackColor = System.Drawing.Color.LightSkyBlue;
            dataGridViewCellStyle3.Font = new System.Drawing.Font("MS UI Gothic", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128)));
            dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.WindowText;
            dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight;
            dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
            dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
            this.bookDataGrid.RowHeadersDefaultCellStyle = dataGridViewCellStyle3;
            this.bookDataGrid.RowHeadersVisible = false;
            this.bookDataGrid.RowHeadersWidth = 82;
            this.bookDataGrid.RowTemplate.Height = 33;
            this.bookDataGrid.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
            this.bookDataGrid.Size = new System.Drawing.Size(869, 521);
            this.bookDataGrid.TabIndex = 0;
            this.bookDataGrid.CellClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.bookDataGrid_CellClick);
            // 
            // 書名DataGridViewTextBoxColumn
            // 
            this.書名DataGridViewTextBoxColumn.DataPropertyName = "書名";
            this.書名DataGridViewTextBoxColumn.HeaderText = "書名";
            this.書名DataGridViewTextBoxColumn.MinimumWidth = 10;
            this.書名DataGridViewTextBoxColumn.Name = "書名DataGridViewTextBoxColumn";
            this.書名DataGridViewTextBoxColumn.ReadOnly = true;
            // 
            // 著者DataGridViewTextBoxColumn
            // 
            this.著者DataGridViewTextBoxColumn.DataPropertyName = "著者";
            this.著者DataGridViewTextBoxColumn.HeaderText = "著者";
            this.著者DataGridViewTextBoxColumn.MinimumWidth = 10;
            this.著者DataGridViewTextBoxColumn.Name = "著者DataGridViewTextBoxColumn";
            this.著者DataGridViewTextBoxColumn.ReadOnly = true;
            // 
            // 値段DataGridViewTextBoxColumn
            // 
            this.値段DataGridViewTextBoxColumn.DataPropertyName = "値段";
            this.値段DataGridViewTextBoxColumn.HeaderText = "値段";
            this.値段DataGridViewTextBoxColumn.MinimumWidth = 10;
            this.値段DataGridViewTextBoxColumn.Name = "値段DataGridViewTextBoxColumn";
            this.値段DataGridViewTextBoxColumn.ReadOnly = true;
            // 
            // bookDataTableBindingSource
            // 
            this.bookDataTableBindingSource.DataMember = "bookDataTable";
            this.bookDataTableBindingSource.DataSource = this.bookDataSet;
            // 
            // bookDataSet
            // 
            this.bookDataSet.DataSetName = "BookDataSet";
            this.bookDataSet.SchemaSerializationMode = System.Data.SchemaSerializationMode.IncludeSchema;
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(35, 589);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(58, 24);
            this.label1.TabIndex = 1;
            this.label1.Text = "書名";
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(35, 655);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(58, 24);
            this.label2.TabIndex = 2;
            this.label2.Text = "著者";
            // 
            // label3
            // 
            this.label3.AutoSize = true;
            this.label3.Location = new System.Drawing.Point(35, 724);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(58, 24);
            this.label3.TabIndex = 3;
            this.label3.Text = "値段";
            // 
            // bookName
            // 
            this.bookName.Location = new System.Drawing.Point(99, 586);
            this.bookName.Name = "bookName";
            this.bookName.Size = new System.Drawing.Size(447, 31);
            this.bookName.TabIndex = 4;
            // 
            // author
            // 
            this.author.Location = new System.Drawing.Point(99, 652);
            this.author.Name = "author";
            this.author.Size = new System.Drawing.Size(447, 31);
            this.author.TabIndex = 5;
            // 
            // price
            // 
            this.price.Location = new System.Drawing.Point(99, 721);
            this.price.Mask = "00000";
            this.price.Name = "price";
            this.price.Size = new System.Drawing.Size(447, 31);
            this.price.TabIndex = 6;
            // 
            // addButton
            // 
            this.addButton.Location = new System.Drawing.Point(646, 568);
            this.addButton.Name = "addButton";
            this.addButton.Size = new System.Drawing.Size(221, 66);
            this.addButton.TabIndex = 7;
            this.addButton.Text = "登録";
            this.addButton.UseVisualStyleBackColor = true;
            this.addButton.Click += new System.EventHandler(this.AddButtonClickedAsync);
            // 
            // removeButton
            // 
            this.removeButton.Location = new System.Drawing.Point(646, 655);
            this.removeButton.Name = "removeButton";
            this.removeButton.Size = new System.Drawing.Size(221, 66);
            this.removeButton.TabIndex = 8;
            this.removeButton.Text = "削除";
            this.removeButton.UseVisualStyleBackColor = true;
            this.removeButton.Click += new System.EventHandler(this.RemoveButtonClicked);
            // 
            // dbReadLabel
            // 
            this.dbReadLabel.AutoSize = true;
            this.dbReadLabel.BackColor = System.Drawing.Color.Transparent;
            this.dbReadLabel.FlatStyle = System.Windows.Forms.FlatStyle.System;
            this.dbReadLabel.Font = new System.Drawing.Font("メイリオ", 10.125F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128)));
            this.dbReadLabel.Location = new System.Drawing.Point(46, 71);
            this.dbReadLabel.Name = "dbReadLabel";
            this.dbReadLabel.Size = new System.Drawing.Size(180, 41);
            this.dbReadLabel.TabIndex = 9;
            this.dbReadLabel.Text = "読み込み中...";
            this.dbReadLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(13F, 24F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(922, 796);
            this.Controls.Add(this.dbReadLabel);
            this.Controls.Add(this.removeButton);
            this.Controls.Add(this.addButton);
            this.Controls.Add(this.price);
            this.Controls.Add(this.author);
            this.Controls.Add(this.bookName);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.bookDataGrid);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "Form1";
            this.Text = "書籍管理";
            ((System.ComponentModel.ISupportInitialize)(this.bookDataGrid)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.bookDataTableBindingSource)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.bookDataSet)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.DataGridView bookDataGrid;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.TextBox bookName;
        private System.Windows.Forms.TextBox author;
        private System.Windows.Forms.MaskedTextBox price;
        private System.Windows.Forms.Button addButton;
        private System.Windows.Forms.Button removeButton;
        private System.Windows.Forms.DataGridViewTextBoxColumn 書名DataGridViewTextBoxColumn;
        private System.Windows.Forms.DataGridViewTextBoxColumn 著者DataGridViewTextBoxColumn;
        private System.Windows.Forms.DataGridViewTextBoxColumn 値段DataGridViewTextBoxColumn;
        private System.Windows.Forms.BindingSource bookDataTableBindingSource;
        private BookDataSet bookDataSet;
        private System.Windows.Forms.Label dbReadLabel;
    }
}

Form1.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace BookManager
{
    public partial class Form1 : Form
    {
        FireStore fireStore;

        public Form1()
        {
            InitializeComponent();

            //// フォームロード時にDataGridViewのタイトル行のスタイルを変更
            //bookDataGrid.ColumnHeadersDefaultCellStyle.BackColor = Color.Azure;
            //bookDataGrid.ColumnHeadersDefaultCellStyle.ForeColor = Color.Black;

            // 読み込み中のラベルを表示
            dbReadLabel.Show();

            // 一定時間ごとにデータベースに変更があったかチェックするタイマーイベント
            Timer refreshTimer = new Timer();
            refreshTimer.Interval = 100;
            refreshTimer.Tick += DataChange;
            refreshTimer.Start();

            // GoolgeのNoSQL DBアクセス用にインスタンスを作成
            fireStore = new FireStore();
            // DBの読み込み等の初期の処理
            fireStore.Initialize();
        }

        // DBが変更されたかチェックするループメソッド
        private void DataChange(object sender, EventArgs e)
        {
            // DBに変更がなければ戻る
            if (!fireStore.IsUpdated) return;

            // 読み取り中のラベル表示
            dbReadLabel.Show();

            // DBからのデータが取得できなければ戻る
            if (fireStore.Books == null) return;

            // 表示のクリア
            bookDataSet.bookDataTable.Clear();

            // DBからデータを取得
            foreach (var book in fireStore.Books)
            {
                // テーブルに表示
                bookDataSet.bookDataTable.Rows.Add
                (
                    book.Name,
                    book.Author,
                    int.Parse(book.Price)
                );
            }

            // DBデータの変更フラグの解除
            fireStore.IsUpdated = false;

            // 読み取り中ラベル非表示
            dbReadLabel.Hide();
        }

        // 登録ボタンのイベントハンドラ
        private async void AddButtonClickedAsync(object sender, EventArgs e)
        {
            // 入力エリアに空白があれば戻る
            if (string.IsNullOrEmpty(bookName.Text) ||
                string.IsNullOrEmpty(author.Text) ||
                string.IsNullOrEmpty(price.Text))
            {
                MessageBox.Show("すべて入力してください");
                return;
            }

            // 入力情報からディクショナリ型のインスタンスを作成
            Dictionary<string, object> book = new Dictionary<string, object>()
            {
                ["Name"] = bookName.Text,
                ["Author"] = author.Text,
                ["Price"] = price.Text,
            };

            // データベースに書き込み
            await fireStore.AddAsync(book);

            // 入力エリアをクリア
            ClearInputArea();
        }

        // 削除ボタンのイベントハンドラ
        private async void RemoveButtonClicked(object sender, EventArgs e)
        {
            Book book = GetSelectRowsData();

            if (book == null) return;

            // データベースから削除
            await fireStore.RemoveAsync(book.Name);

            ClearInputArea();
        }

        private void bookDataGrid_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            var book = GetSelectRowsData();

            if (book == null) return;

            bookName.Text = book.Name;
            author.Text = book.Author;
            price.Text = book.Price;
        }


        // 選択された行の情報を取得
        private Book GetSelectRowsData()
        {
            // 選択されてなければ終わる
            if (bookDataGrid.SelectedRows.Count <= 0) return null;

            // DataGridView の選択された行全体を取得
            DataGridViewRow selectedRow = bookDataGrid.SelectedRows[0];

            // 選択されたセルまたは行から値を抽出 (特定の列名:書名の値を取得)
            string nameCell = selectedRow.Cells["書名DataGridViewTextBoxColumn"].Value.ToString();
            string authorCell = selectedRow.Cells["著者DataGridViewTextBoxColumn"].Value.ToString();
            string priceCell = selectedRow.Cells["値段DataGridViewTextBoxColumn"].Value.ToString();

            return new Book { Name = nameCell, Author = authorCell, Price = priceCell };
        }

        // 入力エリアを消去
        private void ClearInputArea()
        {
            bookName.Text = "";
            author.Text = "";
            price.Text = "";
        }
    }
}

FireStore.cs

using Google.Cloud.Firestore;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace BookManager
{
    internal class FireStore
    {
        // DBインスタスタンス変数
        FirestoreDb db;

        // コレクションインスタンス変数
        CollectionReference bookRef;

        // 更新があったか
        public bool IsUpdated { get; set; }

        // データをBookクラスのリストとして保管
        internal List<Book> Books { get; set; }

        // 初期化処理
        public void Initialize()
        {
            // データベースにアクセスするためのキーのPathを登録
            string accessKeyPath = "practice-395800-firebase-adminsdk-o49jw-48c6c0b78a.json";

            // 環境変数に登録
            Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", accessKeyPath);

            // プロジェクトIDの登録
            string projectId = "practice-395800";

            // データベースのインスタンス作成
            db = FirestoreDb.Create(projectId);

            // データのコレクションのインスタンスを作成
            bookRef = db.Collection("Books");

            // 変更があったかのチェック用
            Query query = bookRef.WhereEqualTo("State", "CA");

            // 変更があった場合のコールバック登録
            FirestoreChangeListener listener = bookRef.Listen(async snapshot =>
            {
                // 変更作業中なら戻る
                if (IsUpdated) return;

                // 変更分の取得
                foreach (var item in snapshot.Changes)
                {
                    string name = item.Document.GetValue<string>("Name");

                    string result = "";

                    switch (item.ChangeType)
                    {
                        case DocumentChange.Type.Removed:
                            result = "削除";
                            break;
                        case DocumentChange.Type.Modified:
                            result = "変更";
                            break;
                        case DocumentChange.Type.Added:
                            result = "追加";
                            break;
                    }

                    await Console.Out.WriteLineAsync($"{name}が{result}されました");

                }

                Books = GetDataFromDBAsync().Result;

                IsUpdated = true;
            });

        }

        internal async Task AddAsync(Dictionary<string, object> book)
        {
            await bookRef.Document(book["Name"].ToString()).SetAsync(book);
        }

        internal async Task RemoveAsync(string name)
        {
            await bookRef.Document(name).DeleteAsync();
        }

        public async Task<List<Book>> GetDataFromDBAsync()
        {
            Task.Delay(100).Wait();

            QuerySnapshot snapshot = await bookRef.GetSnapshotAsync();

            List<Book> books = new List<Book>();

            foreach (DocumentSnapshot document in snapshot.Documents)
            {
                Book book = new Book
                {
                    Name = document.GetValue<string>("Name"),
                    Author = document.GetValue<string>("Author"),
                    Price = document.GetValue<string>("Price"),
                };

                books.Add(book);
            }

            return books;
        }
    }
}

UML図

シーケンス図

クラス図

オブジェクト図

@startuml
actor User
participant Form1 as "Form1"
participant FireStore as "FireStore"
database Firestore as "Firestore DB"

User -> Form1 : Open Form
User -> Form1 : Interact with UI

activate Form1
Form1 -> FireStore : Initialize()
activate FireStore

Form1 -> FireStore : DataChange(sender, e)
FireStore -> Firestore : Check for Updates
Firestore -> FireStore : Updates Found
FireStore -> FireStore : Fetch Data
FireStore -> Form1 : Send Updated Data
deactivate FireStore

Form1 -> User : Update UI with Data

User -> Form1 : Click Add Button
Form1 -> FireStore : AddButtonClickedAsync()
activate FireStore
Form1 -> FireStore : Create Book Object
Form1 -> FireStore : Call AddAsync()
FireStore -> Firestore : Add Data
deactivate FireStore

Form1 -> User : Clear Input Area

User -> Form1 : Click Remove Button
Form1 -> FireStore : RemoveButtonClicked()
activate FireStore
Form1 -> FireStore : Get Selected Rows Data
Form1 -> FireStore : Call RemoveAsync()
FireStore -> Firestore : Remove Data
deactivate FireStore

Form1 -> User : Clear Input Area

User -> Form1 : Click on Data Grid Cell
Form1 -> FireStore : bookDataGrid_CellClick()
Form1 -> FireStore : Get Selected Rows Data
Form1 -> User : Display Selected Data in Input Area

User -> Form1 : Close Form
deactivate Form1

@enduml
@startuml
class Form1 {
  + fireStore: FireStore
  + Form1()
  + DataChange(sender: object, e: EventArgs)
  + AddButtonClickedAsync(sender: object, e: EventArgs)
  + RemoveButtonClicked(sender: object, e: EventArgs)
  + bookDataGrid_CellClick(sender: object, e: DataGridViewCellEventArgs)
  + GetSelectRowsData(): Book
  + ClearInputArea()
}

class FireStore {
  - db: FirestoreDb
  - bookRef: CollectionReference
  - IsUpdated: bool
  - Books: List<Book>
  + Initialize()
  + AddAsync(book: Dictionary<string, object>)
  + RemoveAsync(name: string)
  + GetDataFromDBAsync(): List<Book>
}

class Book {
  - Name: string
  - Author: string
  - Price: string
}

Form1 --> FireStore : fireStore
FireStore --> Book : Books
@enduml
@startuml
object Form1 {
    fireStore: FireStore
}

object FireStore {
    db: FirestoreDb
    bookRef: CollectionReference
    IsUpdated: bool
    Books: List<Book>
}

object Firestore {
    // Firestore DB インスタンス
}

object Book {
    Name: string
    Author: string
    Price: string
}

Form1 --> FireStore : fireStore
FireStore --> Firestore : db
FireStore --> Firestore : bookRef
FireStore --> Book : Books
@enduml

おまけ

Form1.Designer.csファイルをデータとして考慮した場合のUML図です

Formまで含んだクラス図

@startuml
namespace BookManager {
    class Form1 {
        +components : System.ComponentModel.IContainer
        +Dispose(disposing: bool) : void
        +InitializeComponent() : void
        +DataChange(sender: object, e: EventArgs) : void
        +AddButtonClickedAsync(sender: object, e: EventArgs) : async void
        +RemoveButtonClicked(sender: object, e: EventArgs) : async void
        +bookDataGrid_CellClick(sender: object, e: DataGridViewCellEventArgs) : void
        +GetSelectRowsData() : Book
        +ClearInputArea() : void
    }
    
    class FireStore {
        -db : FirestoreDb
        -bookRef : CollectionReference
        +IsUpdated : bool
        +Books : List<Book>
        +Initialize() : void
        +AddAsync(book: Dictionary<string, object>) : Task
        +RemoveAsync(name: string) : Task
        +GetDataFromDBAsync() : Task<List<Book>>
    }
    
    class Book {
        +Name : string
        +Author : string
        +Price : string
    }
    
    class BookDataSet {
        +bookDataTable : DataTable
    }

    Form1 "1" --> "1" FireStore : uses
    Form1 "1" --> "*" Book : uses
    FireStore "1" --> "1" FirestoreDb : uses
    FireStore "1" --> "1" CollectionReference : uses
}
@enduml

Formまで含んだシーケンス図

@startuml
actor User

User -> Form1: Open the Form

activate Form1

User -> Form1: Input book information
Form1 -> Form1: Update input fields

User -> Form1: Click Add button
Form1 -> FireStore: Call AddAsync()

activate FireStore

FireStore -> FireStore: Write data to Firestore

FireStore -> FireStore: Set IsUpdated = true

User <- Form1: Show success message
Form1 <- FireStore: DataChange event

deactivate FireStore

User -> Form1: Click Remove button
Form1 -> FireStore: Call RemoveAsync()

activate FireStore

FireStore -> FireStore: Remove data from Firestore

User <- Form1: Show success message
Form1 <- FireStore: DataChange event

deactivate FireStore

User <- Form1: Close the Form

deactivate Form1
@enduml

オブジェクト図

@startuml
object Form1
object FireStore
object Book
object BookDataSet

Form1 *-- FireStore
Form1 *-- Book
Form1 *-- BookDataSet
FireStore *-- Book

@enduml

参考