C# EntityFrameworkCore リレーションデータの取得、登録、削除

## 始めに
EntityFrameworkCoreを勉強がてらに触ってみて、「リレーションの構築」これは使えそうだなと思ったテクニックをメモしてみました。
作成したソースはメモ置き場として[github](https://github.com/yunomichawan/practiceEFCore)に公開しています。

## 開発環境
・.Net Core 3.1
・[MySql.Data.EntityFrameworkCore(8.0.18)](https://www.nuget.org/packages/MySql.Data.EntityFrameworkCore/8.0.18?_src=template)
・Visual Studio Community 2019
・Windows 10

## 下準備
データベースへの接続環境とテーブルを構築します。

### DB接続
MySQL用にDBの接続設定を行います。
MySQLの接続先は各々の環境で設定してください。

DbContextクラスを継承したクラスを作成し、「OnConfiguring」をオーバーライドします。

```C#
public class SampleEntities : DbContext
{
	// 一部省略

	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
	    base.OnConfiguring(optionsBuilder);
	    optionsBuilder.UseMySQL("MySQL接続文字列");
	}
}
```

### データベース初期化
DBの接続先を変更したら下記コードでデータベースの作成を行います。

```C#

// 接続文字列記載のデータベースを削除
this.sampleEntities.Database.EnsureDeleted();

// 接続文字列記載のデータベースを作成
this.sampleEntities.Database.EnsureCreated();

```
**※注意**
「EnsureDeleted」は接続文字列記載のデータベースを全て削除します。今回は技術検証のため使用しています。
使い方等については[Api の作成と削除-EF Core | Microsoft Docs](https://docs.microsoft.com/ja-jp/ef/core/managing-schemas/ensure-created)を参照ください。

### リレーションの構築
データベースの初期化を行うと次の4つのテーブルを作成します。~~ER図作って~~
PKは全て「ID」としオートインクリメントで構成します。
・M_AREA (MArea.cs) 
・M_SHOP (MShop.cs) ・・・ M_AREAが1に対して複数存在
・T_DAILY_SALES (TDailySales.cs) ・・・ M_SHOPが1に対して複数存在
・T_MONTHLY_SALES (TMonthlySales.cs) ・・・ M_SHOPが1に対して複数存在

関連データの構築はDbContextを継承したクラスの「OnModelCreating」の中で行います。

```C#

public class SampleEntities : DbContext
{
	public DbSet<MArea> Areas { get; set; }

	public DbSet<MShop> Shops { get; set; }

	public DbSet<TDailySales> DailySales { get; set; }

	public DbSet<TMonthlySales> MonthlySales { get; set; }

	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
	    base.OnConfiguring(optionsBuilder);
	    optionsBuilder.UseMySQL("MySQL接続文字列");
	}

	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
	    // エリアと店舗情報で1:nの関係を作成
	    modelBuilder.Entity<MShop>()
	        .HasOne(s => s.Area)
	        .WithMany(a => a.Shops);
	    
	    // 店舗情報と売上高（日別）で1:nの関係を作成
		modelBuilder.Entity<TDailySales>()
		    .HasOne(d => d.Shop)
		    .WithMany(s => s.SalesDailies);

		// 店舗情報と売上高（月別）で1:nの関係を作成
		modelBuilder.Entity<TMonthlySales>()
		    .HasOne(m => m.Shop)
		    .WithMany(s => s.SalesMonthlies);
	}
}
```
リレーション構築に使用するエンティティクラスです。

```C#
[Table("M_AREA")]
public class MArea
{
    [Key]
    [Column("ID")]
    public int Id { get; set; }
    
    /* 長くなるため一部プロパティは省略 */
    
    /// <summary>
    /// 店舗情報
    /// </summary>
    public List<MShop> Shops { get; set; }
}

[Table("M_SHOP")]
public class MShop
{
	[Key]
    [Column("ID")]
    public int Id { get; set; }
    
    // 長くなるため一部プロパティは省略
    
    /// <summary>
    /// エリアID
    /// </summary>
    [Column("AREA_ID")]
    public int AreaId { get; set; }
    
    /// <summary>
    /// エリア
    /// </summary>
    public MArea Area { get; set; }

    /// <summary>
    /// 売上高（日別）
    /// </summary>
    public List<TDailySales> SalesDailies { get; set; }

    /// <summary>
    /// 売上高（月別）
    /// </summary>
    public List<TMonthlySales> SalesMonthlies { get; set; }
}

[Table("T_SALES_DAILY")]
public class TDailySales
{
    [Key]
    [Column("ID")]
    public int Id { get; set; }
    
    // 長くなるため一部プロパティは省略
    
    /// <summary>
    /// 店舗ID
    /// </summary>
    [Column("SHOP_ID")]
    public int ShopId { get; set; }
	
	/// <summary>
    /// 店舗情報
    /// </summary>
    public MShop Shop { get; set; }
}

[Table("T_SALES_MONTHLY")]
public class TMonthlySales
{
    [Key]
    [Column("ID")]
    public int Id { get; set; }

    // 長くなるため一部プロパティは省略

    /// <summary>
    /// 店舗ID
    /// </summary>
    [Column("SHOP_ID")]
    public int ShopId { get; set; }

    /// <summary>
    /// 店舗情報
    /// </summary>
    public MShop Shop { get; set; }
}
```
### リレーション構築に必要なプロパティ名

リレーションを構築する上でプロパティの名前が重要になってきます。
プロパティ名がパターンに則っていない場合、リレーションは構築されません。
そのパターンは4つあり、1つ前のセクションで説明した「MShop」クラスで構築した場合の具体例と共にメモします。

パターン1：\<navigation property name>\<principal key property name>

``` C# 
// このプロパティが「navigation property」
public MArea Area { get; set; }

// 「Area」が「navigation property name」、「Id」が「principal key property name」
[Column("AREA_ID")]
public int AreaId { get; set; }

```

パターン2：\<navigation property name>Id

```C#
// このプロパティが「navigation property」
public MArea Area { get; set; }

// 「Area」が「navigation property name」になり、その後ろに「Id」をつける。
[Column("AREA_ID")]
public int AreaId { get; set; }

```

パターン3：\<principal entity name>\<principal key property name>

```C#

public MArea Area { get; set; }

// 「MArea」が「principal entity name」、「Id」が「principal key property name」
[Column("AREA_ID")]
public int MAreaId { get; set; } 
```

パターン4：\<principal entity name>Id

```C#

public MArea Area { get; set; }

// 「MArea」が「principal entity name」になり、その後ろに「Id」をつける。
[Column("AREA_ID")]
public int MAreaId { get; set; }

```

上記パターンの詳細な説明については[リレーションシップ-EF Core | Microsoft Docs](https://docs.microsoft.com/ja-jp/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key)を参照してください。
ちなみに1対多のほかにで1対1、多対多でのリレーションの組み方などが書いてあります。

## リレーションデータ取得、登録、削除

### リレーションデータ一括取得
リレーションデータの取得は次のように行います。

```C#
using (SampleEntities sampleEntities = new SampleEntities())
{
	// エリア、店舗、売上情報取得
	MArea area = sampleEntities.Areas
	    .Include(a => a.Shops)
	        .ThenInclude(shop => shop.SalesDailies)
	    .Include(a => a.Shops)
	        .ThenInclude(shop => shop.SalesMonthlies)
	        .FirstOrDefault();

}
```

リレーションデータの構築を行っていない場合の取得方法は「Join」や「Linqのクエリ式」があります。
ただ今回はリレーションデータに関するメモのため省きます。。

### リレーションデータ一括登録
リレーションを構築するとオートインクリメントをキーに使用するデータの登録が簡単になります。
「LAST_INSERT_ID()」をINSERT文に組み込むなどの工夫が必要でしたが(私はこんな感じにやってました)、リレーションを構築すると、主キーを自動で紐づけて登録を行ってくれます。

```C#

// エリア作成
MArea area1 = new MArea { AreaName = "北海道" };

// エリアに店舗情報追加
MShop shopHokkaido = new MShop { Area = area1, Address = "北海道xxxxxxxxxx", ShopName = "北海道店舗" };

// 店舗に売上情報追加：北海道
shopHokkaido.SalesDailies = new List<TDailySales>();
shopHokkaido.SalesDailies.Add(new TDailySales { Shop = shopHokkaido, /*紐づけに必要な情報以外は省略*/ });
shopHokkaido.SalesDailies.Add(new TDailySales { Shop = shopHokkaido, /*紐づけに必要な情報以外は省略*/ });
shopHokkaido.SalesDailies.Add(new TDailySales { Shop = shopHokkaido, /*紐づけに必要な情報以外は省略*/ });

shopHokkaido.SalesMonthlies = new List<TMonthlySales>();
shopHokkaido.SalesMonthlies.Add(new TMonthlySales { Shop = shopHokkaido, /*紐づけに必要な情報以外は省略*/ });

using(SampleEntities sampleEntities = new SampleEntities())
{
    // 登録内容の追加
    sampleEntities.Areas.Add(area1);
    sampleEntities.Shops.Add(shopHokkaido);
    
    // 登録内容の保存
    sampleEntities.SaveChanges();
}

```
ちなみに登録前の何も設定していないIDの値を確認すると「-2147482647」から連番になっていることができます。
登録内容の保存後は採番された値が代入されていることを確認できます。

### リレーションデータ一括削除
リレーションデータの物理削除を行います。
リレーションの親要素を削除することで、子要素も自動で削除されます。

``` C#
using(SampleEntities sampleEntities = new SampleEntities())
{
    // エリア情報取得
    // 子要素を取得しなくても、リレーションデータの削除されることを確認。
    MArea area = sampleEntities.Areas.FirstOrDefault();
        //.Include(a => a.Shops)
        //    .ThenInclude(shop => shop.SalesDailies)
        //.Include(a => a.Shops)
        //    .ThenInclude(shop => shop.SalesMonthlies)
        //    .FirstOrDefault();

	// DbSet経由の削除
	sampleEntities.Remove(area);

	// 削除内容の保存
	sampleEntities.SaveChanges()
}
```

### 参考にしたページ
「EF Core | Microsoft Docs」の下記ページを主に参考にしました。
・[リレーションシップ](https://docs.microsoft.com/ja-jp/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key)
・[関連データの読み込み](https://docs.microsoft.com/ja-jp/ef/core/querying/related-data)
・[関連データの保存](https://docs.microsoft.com/ja-jp/ef/core/saving/related-data)
・[連鎖削除](https://docs.microsoft.com/ja-jp/ef/core/saving/cascade-delete)

## 最後に
「EF Core| Microsoft Docs」にはまだまだ有用なテクニック書かれているため、本格的に業務で使うとなれば理解する必要があると感じました。
[データベースの移行](https://docs.microsoft.com/ja-jp/ef/core/managing-schemas/migrations/index?tabs=dotnet-core-cli)に関するテクニックは特に。。