EntityFrameworkCoreを使ってSQLServerを操作する

# はじめに
タイトルの通り、EntityFrameworkCoreを使ってSQLServerを操作する方法について自分なりに整理してみました。
初投稿なので文章が拙いのは見逃してください…
# 環境
・Visual Studio 2022 Comunnity 17.12.3
・SQLServer2022
・Windowsフォームアプリ(.NET8.0)
# 動機
winformsでレジアプリを作っていて、商品情報やら取引履歴やらの管理にSQLServerを使っていたのですが…
コードが無駄に長いorz
一つ追加するだけでも60行もかかるし、forループやらif文やらを使えばネストも深くなってしまいます。
```C#:Form1.cs
using Microsoft.Data.SqlClient;

using (var sqlConnection = new SqlConnection(GetConnectionString()))
{
    try
    {
        //データベースに接続
        sqlConnection.Open();
        Debug.WriteLine("サーバーへの接続に成功しました");
        
        using (var transaction = sqlConnection.BeginTransaction())
        using (var command = sqlConnection.CreateCommand())
        {
            try
            {
                //トランザクションを定義
                command.Transaction = transaction;
                
                //テーブルを挿入するSQLの準備
                command.CommandText = @"INSERT INTO 売上履歴(取引ID,カート内番号,販売時刻,カテゴリー番号,商品番号,商品名,規定価格,最終価格)VALUES(@取引ID,@カート内番号,@販売時刻,@カテゴリー番号,@商品番号,@商品名,@規定価格,@最終価格)";

                command.Parameters.Add(new SqlParameter("@取引ID", SqlDbType.NVarChar) { Value = "1234562412210023"});
                command.Parameters.Add(new SqlParameter("@カート内番号", SqlDbType.Int) { Value = 1 });
                command.Parameters.Add(new SqlParameter("@販売時刻", SqlDbType.DateTime) { Value = DateTime.Now });
                command.Parameters.Add(new SqlParameter("@カテゴリー番号", SqlDbType.NVarChar) { Value = 1 });
                command.Parameters.Add(new SqlParameter("@商品番号", SqlDbType.NVarChar) { Value = 5 });
                command.Parameters.Add(new SqlParameter("@商品名", SqlDbType.NChar) { Value = "大根" });
                command.Parameters.Add(new SqlParameter("@規定価格", SqlDbType.Money) { Value = 100 });
                command.Parameters.Add(new SqlParameter("@最終価格", SqlDbType.Money) { Value = 100 });

                //SQLの実行
                command.ExecuteNonQuery();
                //次の実行のためにパラメーターをクリア
                command.Parameters.Clear();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("エラーが発生しました:\r\n" + ex);
                //ロールバック
                command.Transaction.Rollback();
                throw;
            }
            finally
            {
                command.Transaction.Commit();
            }
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("サーバーへの接続に失敗しました\r\n" + ex);
        throw;
    }
    finally
    {
        //サーバーへの接続終了
        sqlConnection.Close();
    }
}
```
どうにか短く書けないものか…とか思いつつMicrosoftのサイトを彷徨っていたら、どうもEntityFrameworkCore(EFCore)なるものがあるらしい。
調べてみると、テーブルの作成や追跡を自動でやってくれるうえコードも無駄が少なく便利そうだったので使ってみました。
# テーブルの作成
```C#:MyContext.cs
using Microsoft.EntityFrameworkCore;
using System.Configuration;

public class MyContext:DbContext
{
    //DbSet<>の変数名が作成されるテーブル名になる
    public DbSet<StoreData> StoreData { get; set; }
    
    public string connectionString { get; }
    
    public MyContext()
    {
        connectionString = ConfigurationManager.ConnectionStrings["MyTestDB"].ConnectionString;
    }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.UseSqlServer(connectionString);
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //キーの設定
        modelBuilder.Entity<StoreData>(entity =>
        {
            entity.HasKey(e => e.店舗番号);
            entity.HasMany(e => e.TerminalData)
                .WithOne(e => e.StoreData)
                .HasForeignKey(e => e.店舗番号);
        });
    }
}
```
```C#:StoreData.cs
public class StoreData
{
    //テーブルのカラム名と型
    public string 登録番号 { get; set; }
    public int 店舗番号 { get; set; }
    public string 店舗名 { get; set; }
    public string? 住所 { get; set; }
    public string? 電話番号 { get; set; }
    public string? メールアドレス { get; set; }
    public string? 店舗画像 { get; set; }
    public string? 店舗説明 { get; set; }
    
    //外部キーの設定に使う
    public ICollection<TerminalData> TerminalData { get; set; }
    public ICollection<CategoryData> CategoryData { get; set; }
}
```
こんな感じでテーブルのカラム名やキーなどを設定して、パッケージマネージャーコンソールで'Add-Migration 移行名'を実行するとプロジェクト内にフォルダが作成され、移行名クラスが自動生成される。
その後、パッケージマネージャーコンソールで'Update-Database'を実行するとデータベース内にテーブルが作成される。
# CRUD操作
### Create
```C#:Form1.cs
void Create()
{
    using (var context = new MyContext())
    {
        context.StoreData.Add(new POSData
        {
            登録番号="T1234567890123",
            店舗番号=1234,
            店舗名="○○スーパー△△店"
        });
        context.SaveChanges();
    }
}
```
### Read
```C#:Form1.cs
void Read()
{
    using(var context=new MyContext())
    {
        context.StoreData
            .Where(data => data.店舗番号 == "○○スーパー△△店")
            .ToList()
            .ForEach(b => Debug.WriteLine(b));
    }
}
```
### Update
```C#:Form1.cs
void Update()
{
    using(var context=new MyContext())
    {
       var data=context.StoreData
            .Where(data => data.店舗番号 == 1234)
            .FirstOrDefault();
        data.店舗番号 = "○○マート××店";
        context.SaveChanges();
    }
}
```
OrderByを使って小さいものを取得などの操作も可能
### Delete
```C#:Form1.cs
void Delete()
{
    var data=context.StoreData
         .Where(data => data.店舗番号 == 1234)
         .FirstOrDefault();
     context.Remove(data);
     context.SaveChanges();
}
```
# おわりに
ラムダ式に慣れていないと難しかったりキーの設定が若干面倒だったりとちょっとしたデメリットはあるものの、SQLをべた書きするよりもかなりコンパクトに書けました。それだけでなく、エンティティがあるおかげでデータ型の不一致も実行する前に気付けたりとデータの扱いが便利になりました。