Visual C# と MySQL の勉強で簡易的な TODO リストを作って勉強してみた

C# と MySQL で簡易的な TODO リストを作成しました。
最近 C# を勉強し始めたばかりなので、内容に不備などあるかもしれませんがご了承ください。

バージョン

- MySQL 9.0.1
- Visual Studio 2022
- C# 12.0
- .NET 8.0.10

# MySQL の準備

勉強用でちょっと動かすだけなので docker で簡単に用意する。

```yaml:docker-compose.yml
version: "3"

services:
  db:
    image: mysql
    container_name: mysql
    environment:
      # 設定必須、rootパスワード
      - MYSQL_ROOT_PASSWORD=root
      # この設定はオプション、イメージの起動時に作成されるデータベース名
      - MYSQL_DATABASE=sample
    ports:
      - 3306:3306
    volumes:
      - mysql_data:/var/lib/mysql
  phpmyadmin:
      container_name: phpmyadmin
      image: phpmyadmin/phpmyadmin
      environment:
          - PMA_ARBITRARY=1
          - PMA_HOST=mysql
          - PMA_USER=root
          - PMA_PASSWORD=root
      ports:
          - 8080:80
      depends_on:
          - db

volumes:
  mysql_data:
```

`docker-compose.yml` があるフォルダで `docker-compose up` を実行すると、

http://localhost:8080/ で phpmyadmin から MySQL に接続できているので、データベース sample にテーブルを作成する。

```sql
CREATE TABLE `sample`.`todos` (
  `id` int NOT NULL AUTO_INCREMENT,
  `todo` text NOT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
);
```

![img01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/1581d62d-91bc-103b-3f58-03485bb1d4f9.png)


# Visual Studio でプロジェクト作成

![img02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/8b4652fa-4954-7848-6296-340136b80f6b.png)

![img03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/8a8130c4-75cd-f1a6-202c-7d70669b239f.png)


Visual Studio の画面から DataGridView を配置する。

![img05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/2726ce65-ae3c-fda2-93f7-74afc9a0cc82.png)


---

MySql.Data を NuGet パッケージ管理から入れる。

https://www.nuget.org/packages/MySql.Data/9.1.0?_src=template

MySql.Data をラップして少し使いやすくしたクラスを作成する。

```csharp:Db.cs
using MySql.Data.MySqlClient;

namespace TodoList
{
    internal class Db
    {
        private static MySqlConnection? _connection;

        private static MySqlConnection GetConnection()
        {
            if (_connection == null)
            {
                _connection = new MySqlConnection("server=127.0.0.1;uid=root;pwd=root;database=sample");
                _connection.Open();
            }
            return _connection;
        }

        public static void Execute(string sql, object?[]? param = null)
        {
            var command = new MySqlCommand(sql, GetConnection());
            Array.ForEach(param ?? [], p => command.Parameters.Add(new MySqlParameter("", p)));
            command.ExecuteNonQuery();
        }

        public static string[][] ExecuteAndFetchAll(string sql, object?[]? param = null)
        {
            var lines = new List<string[]>();

            var command = new MySqlCommand(sql, GetConnection());
            Array.ForEach(param ?? [], p => command.Parameters.Add(new MySqlParameter("", p)));
            using var reader = command.ExecuteReader();
            while (reader.Read())
            {
                string[] line = new string[reader.FieldCount];
                for (int i = 0; i < reader.FieldCount; i++)
                {
                    line[i] = reader.GetValue(i)?.ToString() ?? "";
                }
                lines.Add(line);
            }

            return lines.ToArray();
        }
    }
}
```

Form1 のコードで Todo リストを作る。

```csharp:Form1.cs
using System.Data;

namespace TodoList
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            InitGrid();
        }

        private void InitGrid()
        {
            dataGridView1.DataSource = makeGridView();

            // 変更できないカラムを設定する
            foreach (int key in new List<int> { 0, 2, 3 })
            {
                dataGridView1.Columns[key].ReadOnly = true;
                dataGridView1.Columns[key].DefaultCellStyle.BackColor = Color.LightGray;
            }

            // "更新する"ボタンの列を追加
            var updeatColumn = new DataGridViewButtonColumn();
            updeatColumn.Name = "更新";
            updeatColumn.UseColumnTextForButtonValue = true;
            updeatColumn.Text = "更新する";
            dataGridView1.Columns.Add(updeatColumn);

            // "削除する"ボタンの列を追加
            var delColumn = new DataGridViewButtonColumn();
            delColumn.Name = "削除";
            delColumn.UseColumnTextForButtonValue = true;
            delColumn.Text = "削除する";
            dataGridView1.Columns.Add(delColumn);

            // データがない一番下の行にも"更新する"ボタンを表示する
            Action drawBtnText = () => dataGridView1.Rows[dataGridView1.Rows.Count - 1].Cells["更新"].Value = "更新する";
            dataGridView1.RowsAdded += (object? sender, DataGridViewRowsAddedEventArgs e) => drawBtnText();

            // データ更新イベント設定
            dataGridView1.CellContentClick += upsert;
            dataGridView1.CellContentClick += delete;
        }

        private DataTable makeGridView()
        {
            var dataTable = new DataTable();

            dataTable.TableName = "Todos";

            dataTable.Columns.Add("id");
            dataTable.Columns.Add("内容");
            dataTable.Columns.Add("作成日");
            dataTable.Columns.Add("更新日");

            var todos = Db.ExecuteAndFetchAll("SELECT * FROM todos");
            foreach (string[] row in todos)
            {
                dataTable.Rows.Add(row);
            }
            return dataTable;
        }

        private void upsert(object? sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex != dataGridView1.Columns["更新"].Index || e.RowIndex == -1)
            {
                // 更新ボタンを押してない場合は何もしない
                return;
            }

            var isNewRow = false;
            if (!int.TryParse(dataGridView1.Rows[e.RowIndex].Cells["id"].Value?.ToString(), out var id))
            {
                // id が取得できない場合は新規レコード作成する
                isNewRow = true;
            }

            var todoTxt = dataGridView1.Rows[e.RowIndex].Cells["内容"].Value?.ToString();
            if (string.IsNullOrEmpty(todoTxt))
            {
                MessageBox.Show("内容を設定してください");
                return;
            }


            if (isNewRow == true)
            {
                Db.Execute("INSERT INTO todos (todo, created_at, updated_at) VALUES (?, ?, ?)", [todoTxt, DateTime.Now, DateTime.Now]);
            }
            else
            {
                Db.Execute("UPDATE todos SET todo = ?, updated_at = ? WHERE id = ?", [todoTxt, DateTime.Now, id]);
            }

            dataGridView1.DataSource = makeGridView();
        }

        private void delete(object? sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex != dataGridView1.Columns["削除"].Index || e.RowIndex == -1)
            {
                // 削除ボタンを押してない場合は何もしない
                return;
            }

            if (!int.TryParse(dataGridView1.Rows[e.RowIndex].Cells["id"].Value?.ToString(), out var id))
            {
                MessageBox.Show("id が取得できません");
                return;
            }

            Db.Execute("DELETE FROM todos WHERE id = ?", [id]);

            dataGridView1.DataSource = makeGridView();
        }
    }
}
```

簡単に Todo リストを作成できました。

![img06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/b05b2c29-87dc-b155-ec22-d4a95f611a3a.png)


---

参考

https://dobon.net/vb/dotnet/datagridview/datagridviewbuttoncolumn.html