.NET Core + MySQL (+ LINQ)でデータベースを扱うはじめの一歩

## はじめに
初めて.NET Core と MySQL でデータベースを扱うことになったので備忘録として残そうと思います。
[今回作ったサンプル](https://bitbucket.org/obisato/greengrocer/src/master/) 
[参考にしたサイト](https://densan-labs.net/tech/codefirst/adddelete.html)

## 環境
- MySQL 8.0.19
- .NET Core 3.1

## テスト用のテーブルをMySQLから作成

- server: localhost
- user:  root
- password: password
- database: mydb

テーブル作成。
テーブル名に複数形のsをつけないと文法エラーが出るようです。

```sql

CREATE TABLE IF NOT EXISTS products (
    id INT,
    name VARCHAR(32) NOT NULL UNIQUE,
    price INT NOT NULL,
    quantity INT NOT NULL,
    PRIMARY KEY (id)
);
```

## 新規プロジェクト作成
.NET Core のコンソールアプリケーションのプロジェクトを作成します。

```sh
$ dotnet new console
```

MySql.Data.EntityFrameworkCore をインストール。

```sh 
$ dotnet add package MySql.Data.EntityFrameworkCore
$ dotnet restore
```

## .csproj
一応載せておきます。

```
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MySql.Data.EntityFrameworkCore" Version="8.0.19" />
  </ItemGroup>

</Project>
```

## 中身
次のクラスを作ります。

 - テーブルのカラムと同じプロパティを持つEntityクラス
 - オブジェクトの状態とデータベースを結びつけるDbContextクラス
 - 上の２つを使って実際に操作の指示を出すメソッドを持つDataOperationクラス

それぞれ載せておきます。
#### Entityクラス
※今回真偽値は使っていませんが、booleanはMySQLのデータ型にないのでマップできません。bit(1)を使うとよいそうです。( [参考](https://qiita.com/ka215/items/379c4d46d0c04b7fdb46) )

```C#:Product.cs
    public class Product
    {
        public int id { get; set; }
        public string name { get; set; }
        public int price { get; set; }
        public int quantity { get; set; }
    }
```
#### DbContextクラス
Microsoft.EntityFrameworkCore.DbContextクラスを継承します。
`DbSet<>`の要素がEntityクラスで定義したものになります。
※`UseMySQL()`内を適切に書き換えてください。直書きですみません...。

```C#:GreengrocerContext.cs
    public class GreengrocerContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseMySQL(@"server=localhost;database=mydb;userid=root;pwd=password;sslmode=none;");
    }
```

#### DataOperationクラス
基本的な操作のメソッドをいくつか作ってみました。
※LINQの`Any()`を使おうとすると、エラーが出ます。( [詳細](https://bugs.mysql.com/bug.php?id=92987) )
`FindOrDefault()`が`null`かどうかを調べることで同じことができます。
※`AsNoTracking()`すると、オブジェクトの状態をデータベースに反映させません。データベースを参照するだけのときは、入れたほうが効率的だそうです。( [詳細](https://docs.microsoft.com/en-us/ef/core/querying/tracking) )

```C#:DataOperation.cs
public class DataOperation : IDisposable
    {
        private readonly GreengrocerContext context = new GreengrocerContext();
        public DataOperation(GreengrocerContext context)
        {
            this.context = context;
        }

        public void Dispose()
        {
            context.Dispose();
        }

        //データの挿入
        public void AddProduct(int id, string name, int price, int quantity)
        {
            Product newProduct = new Product
            {
                id = id,
                name = name,
                price = price,
                quantity = quantity
            };
            context.Products.Add(newProduct);
            context.SaveChanges();
        }

        //データの削除
        public void RemoveProduct(int id)
        {
            Product product = context.Products.FirstOrDefault(x => x.id == id);
            if (product != null)
            {
                context.Products.Remove(product);
                context.SaveChanges();
            }
        }

        //データの更新
        public void UpdateProductToHalfPrice(int id)
        {
            Product product = context.Products.FirstOrDefault(x => x.id == id);
            if (product != null)
            {
                product.price /= 2;
                context.SaveChanges();
            }
        }

        //全データの取得
        public List<Product> GetAllProducts()
        {
            return context.Products.ToList();
        }

        //特定のデータの取得
        public Product GetSingleProduct(string name)
        {
            return context.Products.AsNoTracking().FirstOrDefault(x => x.name == name);
        }

        //特定のデータの存在確認 ※Any()は使えません!!
        public bool AnyProducts(string name) {
            return context.Products.AsNoTracking().FirstOrDefault(x => x.name == name) != null;
        }

        //特定のデータの個数
        public int CountProductAlmostOutOfStock()
        {
            return context.Products.Count(x => x.quantity < 10);
        }
    }
```

#### これらのクラスの使用例
データを追加して、それを出力してみます。

```C#:Prigram.cs
    class Program
    {
        static void Main()
        {
            GreengrocerContext context = new GreengrocerContext();
            using DataOperation dataOpration = new DataOperation(context);
            dataOpration.AddProduct(1, "Tomato  ", 100, 30);
            dataOpration.AddProduct(2, "Cucumber", 40, 50);
            dataOpration.AddProduct(3, "Onion   ", 50, 100);
            dataOpration.AddProduct(4, "Eggplant", 80, 20);

            List<Product> products = dataOpration.GetAllProducts();
            Console.WriteLine("ID\tName    \tPrice\tQuantity");
            foreach (Product product in products)
            {
                Console.WriteLine("{0}\t{1}\t{2}\t{3}", product.id, product.name, product.price, product.quantity);
            }
        }
    }
```

## 以上です。
ご指摘、アドバイスなど歓迎いたします。