C#（ASP.NET Core webapi）でMySQLを使うぞ？

# 目次
1. [はじめに](#とある日)
2. [使用環境](#環境)
3. [プロジェクトの作成](#プロジェクト作成)
4. [実装](#書いていこう)
5. [実行](#成果物)
6. [終わりに](#終わりに)

# とある日。。。
天井を見ながらこう思った。「あ～C#でMySQL使いてぇ～。」

# というわけで
作っていこうと思う。対象者は、C#でMySQLをイジメたい人向けです。

# 環境
- VSCode with C#(.NET) extension
- .NET 8.0.11 SDK
- Docker

# プロジェクト作成
以下のコマンドでプロジェクトを作成する
```powershell
E:\Project> dotnet new webapi -o DatabaseApi
E:\Project> cd DatabaseApi
E:\Project\DatabaseApi> dotnet new sln
E:\Project\DatabaseApi> dotnet sln add .
```

# NuGetパッケージの追加
MySQLをC#上で使えるようにするため、パッケージを追加する
```powershell
E:\Project\DatabaseApi> dotnet add package MySql.EntityFrameworkCore --version 8.0.8
```

# 書いていこう
テーブル操作を統一するために、リポジトリインターフェースを実装する
お好みに改造してかまわない
```cs:IRepository.cs
namespace DatabaseApi;

///<summary>
/// テーブル操作インターフェース
/// <p>
/// T - テーブルデータクラス
/// </p>
///</summary>
public interface IRepository<T> where T : class
{
    protected Task<T> CreateAsync(T newData);
    protected Task<T> UpdateAsync(T updatedData);
    protected Task<bool> DeleteAsync(T targetData);
}
```

データベース操作用の基底コンテキストクラスを作成する
```cs:BaseDbContext.cs
using Microsoft.EntityFrameworkCore;

namespace DatabaseApi;

public abstract partial class BaseDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        throw new NotImplementedException();
    }
}
```

データベース用のコンテキストクラスを作成する
```cs:ExampleDbContext.cs
#pragma warning disable CS8618
using Microsoft.EntityFrameworkCore;
using MySql.Data.MySqlClient;

namespace DatabaseApi;

public partial class ExampleDbContext() : BaseDbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connectionStr = new MySqlConnectionStringBuilder()
        {
            Server = "localhost",
            Port = 3306,
            UserID = "root",
            Password = "P@ssw0rd",
            Database = "Example"
        }.ToString();

        optionsBuilder.UseMySQL(connectionStr);
    }
}
```

テーブル用のデータオブジェクトクラスを追加する
```cs:Example_TableA.cs
using System.ComponentModel.DataAnnotations;

namespace DatabaseApi;

public class TableA
{
    [Key]
    public string Id {get; set;} = null!;

    public string Name {get; set;} = string.Empty;
    public string Birthday {get; set;} = string.Empty;
}
```

データベース用のコンテキストクラスにテーブルを追加する
```cs:ExampleDbContext.cs
#pragma warning disable CS8618
using Microsoft.EntityFrameworkCore;
using MySql.Data.MySqlClient;

namespace DatabaseApi;

public partial class ExampleDbContext() : BaseDbContext, IRepository<TableA>
{
    public DbSet<TableA> Users {get; private set;}

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        ...
    }

    public async Task<TableA> CreateAsync(TableA newData) { ... }
    public async Task<TableA> UpdateAsync(TableA updatedData) { ... }
    public async Task<TableA> DeleteAsync(TableA targetData) { ... }
}
```
:::note warn
同期関数でも大丈夫ですが、同時にいくつも処理を行う場合は非同期にしてください。
:::

処理を追加する
```cs:ExampleDbContext.CreateAsync(T newData)
public async Task<TableA> CreateAsync(TableA newData)
{
    await Users.AddAsync(newData);
    await SaveChangesAsync();

    return newData;
}
```
```cs:ExampleDbContext.UpdateAsync(T updatedData)
public async Task<TableA> UpdateAsync(TableA updatedData)
{
    var target = await Users.FirstOrDefaultAsync(x => x.Id == updatedData.Id);

    if (target == null) throw new Exception("Target not found.");

    target.Name = updatedData.Name;
    target.Birthday = updatedData.Birthday;
    
    Users.Update(target);
    await SaveChangesAsync();

    return target;
}
```
```cs:ExampleDbContext.DeleteAsync(T targetData)
public async Task<TableA> DeleteAsync(TableA targetData)
{
    var target = await Users.FirstOrDefaultAsync(x => x.Id == targetData.Id);

    if (target == null) throw new Exception("Target not found.");

    try {
        Users.Remove(target);
        await SaveChangesAsync();
        return true;
    } catch {
        return false;
    }
}
```

webapiを書き換える
```cs:Program.cs
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton(new DatabaseApi.ExampleDbContext());
builder.Services.AddOpenApi();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();

app.MapGet("/get", async (DatabaseApi.ExampleDbContext db) =>
{
    return await db.Users.ToListAsync();
});

app.MapPost("/add", async (DatabaseApi.ExampleDbContext db) =>
{
    var newData = new DatabaseApi.TableA()
    {
        Id = "1",
        Name = "John",
        Birthday = DateTime.Now.ToString()
    };
    return await db.CreateAsync(newData);
});

app.MapPost("/update", async (DatabaseApi.ExampleDbContext db) =>
{
    var newData = new DatabaseApi.TableA()
    {
        Id = "1",
        Name = "Josh",
        Birthday = DateTime.Now.ToString()
    };
    return await db.UpdateAsync(newData);
});

app.MapDelete("/remove", async (DatabaseApi.ExampleDbContext db) =>
{
    var newData = new DatabaseApi.TableA()
    {
        Id = "1"
    };
    return await db.DeleteAsync(newData);
});

app.Run();
```

これで、一通りの処理を追加したのでこれで使えるはず。
、、、DB無くね？

### DockerコンテナでMySQLを起動する
以下のファイルを、プロジェクトフォルダの.containerフォルダ（ないと思うので作ってください）に作成する
```Dockerfile:MySql.Dockerfile
FROM mysql:latest

# この書き方は望ましくない（非推奨）ので心配なら別の方法をググってください
ENV TZ="Asia/Tokyo"
ENV MYSQL_ROOT_PASSWORD="P@ssw0rd"
ENV MYSQL_DATABASE="Example"

EXPOSE 3306

COPY ./init.sql /docker-entrypoint-initdb.d
```
```sql:init.sql
USE Example;

CREATE TABLE IF NOT EXISTS Users (
    Id VARCHAR(255) NOT NULL PRIMARY KEY,
    Name VARCHAR(255) NOT NULL,
    Birthday VARCHAR(255)
);
```
```powershell
E:\Project> docker build --pull --rm -f ".container\MySql.Dockerfile" -t mysql:latest ".container"
E:\Project> docker run -itd -p 3306:3306 mysql
```

# 成果物
VSCodeの拡張機能の`Thunder Client`などを使用して以下の順序でリクエストを送信してみてください。すると、データを操作できていることがわかるはずです。もし、MySQLの設定が違ったり、データベースやテーブルの構造が異なったりする場合は、例外が発生します。
1. http://localhost:5180/add
2. http://localhost:5180/get
3. http://localhost:5180/update
4. http://localhost:5180/remove
5. http://localhost:5180/get

:::note warn
ポート番号はプロジェクトの生成時に決定するため、人それぞれです。起動ログから確認できます。
```shell
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5180 <-------------- ココ！！
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: E:\Project\DatabaseApi
```
:::

# 終わりに
これでC#を使って、MySQLを利用できるようになりましたね！
改善点や修正点などあれば、随時、編集リクエスト、コメントお待ちしております。

# 追記

:::note warn
この`MySql.EntityFrameworkCore`では、以下の脆弱性が警告として出ます。
```shell
warning NU1903: パッケージ 'Microsoft.Extensions.Caching.Memory' 8.0.0 に既知の 高 重大度の脆弱性があります
warning NU1903: パッケージ 'System.Text.Json' 8.0.4 に既知の 高 重大度の脆弱性があります
warning NU1903: パッケージ 'Microsoft.Extensions.Caching.Memory' 8.0.0 に既知の 高 重大度の脆弱性があります
warning NU1903: パッケージ 'System.Text.Json' 8.0.4 に既知の 高 重大度の脆弱性があります
```
:::