.NET 5 × Entity Framework Core でサクッとデータベースとシードデータを作成する

f:id:tanabebe:20210317221145p:plain

1. 環境

2. Entity Framework Coreとは

.NET用のORMです。2016年中頃に.NET Coreへ導入されました。
Entity Frameworkを完全に書き換えたものなので、パフォーマンス面でも大幅に改善されています。もちろんLINQ対応しています。
ざっくり言ってしまうと通常記述しなければいけないコードを簡略化できる便利な仕組みです。
※参考 - Entity Framework Core の概要

サクッと行けるのでやっていきます。
今回作成したプログラムはGitHubにあげています。

github.com

3. プロジェクトの準備

.NETもしっかりCLIがあるのでコマンドを叩いて準備していきます。まずはディレクトリを作成します。

❯❯❯ mkdir DatabaseTest
❯❯❯ cd DatabaseTest/

次はソリューションファイルとプロジェクトを作成します。

DatabaseTest ❯❯❯ dotnet new sln
DatabaseTest ❯❯❯ dotnet new webapi -n Api

どのようなテンプレートを扱えるか忘れた場合、以下のコマンドを打つと良いです。

DatabaseTest ❯❯❯ dotnet new -l

とりあえず困ったらdotnet -hでヘルプを見ましょう。

ソリューションにプロジェクトを追加します。

DatabaseTest ❯❯❯ dotnet sln add Api

4. VS Codeで作業していく

ここからはVS Codeで進めていきます。

まず、以下のNuGet Packageをインストールします。

Nuget Gallery拡張機能がインストールされている前提です。

Nuget Gallaryを開くと以下のような画面になるのでプロジェクトを選択し、インストールします。

f:id:tanabebe:20210317111842p:plain

4.1. モデル用クラスを追加

実際は責務を分けるために別プロジェクトで作成するのが良いですが、今回はApiプロジェクトにまとめていきます。

// DatabaseTest/Api/Model/User.cs
public class User
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public double Height { get; set; }
    public double Weight { get; set; }
}

4.2. DbContextを継承した派生クラスを作成

DataContextのコンストラクタがないとエラーを吐くので忘れずに追加しましょう。VS Codeだと .のショートカットキーを使うとサクッと生成出来ます。
ちなみに変数名がテーブル名となり、この例だとUsersというテーブルが最終的に作成されます。

// DatabaseTest/Api/DataService/DataContext.cs
public class DataContext : DbContext
{
    public DataContext(DbContextOptions options) : base(options)
    {
    }
    public DbSet<User> Users { get; set; }
}

4.3. Startup.csの修正

以下の通り、Startup.csを修正してSQLiteの依存関係を追加します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "Api", Version = "v1" });
    });
    // 追加
    services.AddDbContext<DataContext>(opt =>
    {
        opt.UseSqlite(Configuration.GetConnectionString("DefaultConnections"));
    });
}

Api/appsettings.Development.jsonに接続文字列を追加します。

{
  "ConnectionStrings": {
    "DefaultConnection": "Data source=database.db"
  }
}

ついでにApi/Properties/launchSettings.jsonも修正します。アプリケーション実行時に毎回ブラウザが起動するのは煩わしいので、対象プロジェクト(ここではApi)に記述されているlaunchBrowserfalseにし、ブラウザ起動を抑止します。

{
    "Api": {
      "commandName": "Project",
      "dotnetRunMessages": "true",
      "launchBrowser": false,
      "launchUrl": "swagger",
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
}

データを投入するクラスを作成します。

// Api/DataService/Seed.cs
public static async Task UserData(DataContext context)
{
    if(context.Users.Any()) return;

    var users = new List<User>
    {
        new User 
        {
            Name = "Suzuki",
            Age = 20,
            Height = 172.1,
            Weight = 60.4
        },
        new User
        {
            Name = "Tanaka",
            Age = 30,
            Height = 182.5,
            Weight = 72.4
        },
        new User
        {
            Name = "Yamada",
            Age = 25,
            Height = 160.5,
            Weight = 48.9
        },
        new User
        {
            Name = "Sato",
            Age = 22,
            Height = 175.6,
            Weight = 65.2
        }
    };
    await context.Users.AddRangeAsync(users);
    await context.SaveChangesAsync();
}

dotnet-efがインストールされているか確認します。

DatabaseTest ❯❯❯ dotnet tool list --global
パッケージ ID       バージョン      コマンド
-----------------------------------
dotnet-ef      5.0.4      dotnet-ef

dotnet-efが表示されていなければインストールしましょう。もし、既にインストールされていれば以下のコマンドはスキップしてOKです。

DatabaseTest ❯❯❯ dotnet tool install --global dotnet-ef

マイグレーションします。

DatabaseTest ❯❯❯ dotnet ef migrations add InitialCreate -s Api/

ここまで来たらdotnet ef database updateのコマンドを打つことでデータベースが作成出来ますが、今回はアプリケーション実行時にデータベースの作成、シードデータを投入したいので最後にProgram.csMainメソッドを修正します。

// Api/Program.cs
public static async Task Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    // C# 8.0以降
    using var scope = host.Services.CreateScope();
    var services = scope.ServiceProvider;

    try
    {
        // GetServiceでnullチェックでも良い
        var context = services.GetRequiredService<DataContext>();
        // 待機しないと怒られます
        await context.Database.MigrateAsync();
        // シードデータ投入
        await Seed.UserData(context);
    } catch (Exception e)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(e, "migrateでエラーが発生しました。");
    }
    await host.RunAsync();
}

ようやくアプリケーションの実行です。dotnet watch runとしているのはファイルの更新を検出し、自動的にビルド、アプリケーション起動が出来るので常用しています。

DatabaseTest ❯❯❯ cd Api/
Api ❯❯❯ dotnet watch run

実行するとターミナルにログが出力されます。内容を見るとCREATE TABLEINSERTが実行されているのがわかります。appsettings.Development.jsonで設定したdatabase.dbファイルが作成されているので確認します。
VS CodeのコマンドパレットからSQLiteを選択します。

f:id:tanabebe:20210317111847p:plain

database.dbを選択します。

f:id:tanabebe:20210317111853p:plain

SQLite Explorerが表示されるのでUsersテーブルが作成されていることが確認出来ました。最後にデータが投入されているか確認します。
問題なく作成されています。良かった良かった。

f:id:tanabebe:20210317111900p:plain

5. まとめ

Visual Studioからポチポチとやってしまう事が多かったので、CLIVS Codeで作成するケースを書き起こしてみました。
今回はモデルに変更を加えた後にどういった手順を踏むかについては触れていませんが、ゴリゴリとプログラムを書くことなく、データベースとテーブルの作成、シードデータの投入が出来ました。
なんて楽なんだ。あとはControllerDataContextを注入して、LINQで書いていく感じです。公式にもチュートリアルがあるので、以下を参考にするのも良いです。

docs.microsoft.com

こちらを見る限り、データ操作だとORMに関してはEntity Framework CoreとDapperが書かれているので、ORMを使った場合はこの2択なのかな?というところです。
2016年の記事ですが、以下でパフォーマンスのデータを提供している記事もありました。Dapperも今度試します。

docs.microsoft.com