.NET Core(Entity Framework Core)でCode Firstする

Entity Framework Core 1.1 Preview1でのCode First(コード・ファースト)による開発(というか、まず初めにC#でモデルクラスを定義。モデルクラスからデータベース定義を自動生成の流れ)について見ていきたいと思います。

「コード・ファースト」という言葉については、ここでは詳細は触れません(ググればいっぱい出てきますよね・・・)。

簡単にいうとソフトウェアを開発するにあたり、「リレーショナルデータベース設計ありき」ではなくて「作成するソフトウェアの”ドメイン領域の分析”に注力し、データベースはモデルのあるタイミングにおけるシリアライズ結果を保存する入れ物」的な考え方が基本となっていると思います。
リレーショナルデータベースとドメインオブジェクトは、その概念の相違から「インピーダンス・ミスマッチ」が発生することは必然であり、ソフトウェア側の人間としては、ドメイン領域のオブジェクトモデリングに集中すべき、という思想ですね。

とはいえ、リレーショナルデータベース固有の「高パフォーマンスを発揮する為のテーブル設計やSQLコマンド」のような概念もあり、一概にドメイン分析至上主義な設計ばかりできるわけではないことも事実です。

環境

まず本投稿における検証環境ですが・・・Mac(OSx)で dotnet --infoの結果は以下の通りです。

ryuichi:efCoreExample daigo$ dotnet --info
.NET Command Line Tools (1.0.0-preview2-1-003155)

Product Information:
 Version:            1.0.0-preview2-1-003155
 Commit SHA-1 hash:  d7b0190bd4

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  10.12
 OS Platform: Darwin
 RID:         osx.10.12-x64
ryuichi:efCoreExample daigo$ 

つまり、.NET Core 1.1 Preview1(SDK 1.0.0Preview2.1)となります。

.NET Core プロジェクトの作成

.NET Coreプロジェクトを作成します。ここでは単純なコンソールアプリケーションを作成します。
任意のフォルダ(ここでは /Users/daigo/projects/efCoreExample )にプロジェクトを作成します。

dotnet new

ls -lの結果は以下となります(自動生成されたソース一覧)。

-rwxr--r--  1 daigo  staff  202 10  6 13:55 Program.cs
-rwxr--r--  1 daigo  staff  367 10 14 09:48 project.json

モデルクラスの作成

モデルクラスを作成します。
ここでは、Compeny / Employee の2つのモデルクラスを定義します。
実装は以下の通りです。

// Company.cs
using System.Collections.Generic;

namespace EfCoreExample.Models
{
    public class Company 
    {
        public int id { get; set; }
        public string Name { get; set; }
        public int Capital { get; set; }
        public List<Employee> Employees { get; set; }
    }
}
// Employee.cs
namespace EfCoreExample.Models
{
    public class Employee 
    {
        public int id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

※モデルクラスにはPrimaryKeyを用意する必要があります。Entity Frameworkの作法として「id」「Id」もしくは「【モデル名】Id」という名称のプロパティが存在した場合、これがPrimaryKeyであると自動で判断されます。DataAnnotationで [key] を明示的に指定する方法もあります。

DbContextの作成

Company / Employeeをデータベースに出し入れするためのDbContextクラスを定義します。
OnConfiguring()メソッド中でSQL Serverへの接続文字列を設定していますが、ここでの接続先は私の Azure SQL Database としています。

// CoreExampleContext.cs
using Microsoft.EntityFrameworkCore;

namespace EfCoreExample.Models
{
    public class CoreExampleContext : DbContext
    {
        public DbSet<Company> Company { get; set; }
        public DbSet<Employee> Employee { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=tcp:rdexampledb1svr.database.windows.net,1433;Initial Catalog=RdExampleDb1;Persist Security Info=False;User ID=[user];Password=[password];MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
        }
    }
}

project.jsonの修正

モデルクラスからコード・ファーストでSQL Serverデータベーステーブルへリバースを行うために、Entity Framework Coreのツール及びSQL Serverデータプロバイダが必要になります。これらをproject.jsonに dependencies 及び tools として明示的に参照宣言する必要があります。
いかが修正を加えた project.json です。

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true
  },
  "dependencies": {
    "Microsoft.EntityFrameworkCore.Design":{
      "type":"build",
      "version": "1.0.1"
    },
    "Microsoft.EntityFrameworkCore.SqlServer": "1.0.1"
  },
  "tools": {
    "Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-preview3-final"
  },
  "frameworks": {
    "netcoreapp1.1": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.1.0-preview1-001100-00"
        }
      },
      "imports": "dnxcore50"
    }
  }
}

ここまでの準備を終えた段階で dotnet restore を実行します。

dotnet restore

f:id:daigo-knowlbo:20161108013936p:plain いい感じに、必要な依存関係モジュールを引っ張ってくれているはずです。

Migration準備

モデルクラスをSQL Serverデータベース テーブルに反映させるための コード を自動生成します。
以下のコマンドを実行します。

dotnet ef migrations add db1.0

efコマンドは、 project.json において「Microsoft.EntityFrameworkCore.Tools.DotNet」をtoolsとして指定した上で、dotnet restore を実行した為に利用可能となったオプションコマンドです。
「db1.0」は任意の名称をつける事ができます。今後、モデルクラスの追加 や 既存モデルクラスへのプロパティ追加 が発生した際に、変更セット名称的な意味合いの名称として変更していきます。

f:id:daigo-knowlbo:20161108014525p:plain

コマンド実行後 Migrations という名称のディレクトリが作成されます。この段階では、まだデータベースへの定義の反映は行われていません。

Migrationsディレクトリ配下の ls -l 結果は以下の通りです。
csコードが出力され、その中身はモデルクラスに該当するデータベーステーブルをCREATEする内容となっています。

f:id:daigo-knowlbo:20161108015134p:plain

データベースへテーブル定義(モデルクラス定義)を反映させる

ではデータベースに対して、物理的にモデルクラス定義反映させます。つまり、モデルクラスに該当する テーブル定義 を追加します。
以下のコマンドを実行します。

dotnet ef database update

f:id:daigo-knowlbo:20161108015647p:plain

データベーステーブルの確認

モデルクラス定義に従って、データベーステーブルが作成されていることを確認します。
以下がその結果であり、Company / Employee の各テーブル及びカラム定義がモデルクラスに対応する形で作成されていることを確認することができます。
また、_EFMigrationsHistoryテーブルはコードファーストでモデルクラスから物理テーブルへのリバース(マイグレーション)が行われた履歴情報を保存しています。

f:id:daigo-knowlbo:20161108015904p:plain

モデルクラスのアップデート・・・そしてテーブル定義もアップデート

開発初期バージョンは上記までの実装で無事行われたと想定します。
しかし、途中でモデルにプロパティの追加の必要性が発生したことを想定してみましょう。
影響範囲は「モデルクラス定義の更新(プロパティ追加)」及び「モデルクラスに対応するデータベーステーブルの更新(カラム追加)」となります。
まずモデルクラスの変更は、以下のようにEmployeeクラスに生年月日プロパティが追加になったと想定します。

// Employee.cs
using System;

namespace EfCoreExample.Models
{
    public class Employee 
    {
        public int id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public DateTime Birth { get; set; }  // 追加!!!!
    }
}

モデルクラスの修正が完了したら、再び以下の dotnet ef コマンドを実行します。

dotnet ef migrations add db1.1
dotnet ef database update

add以下のパラメータを「db1.1」としました。これは先程の「db1.0」からのバージョンアップによるmigrationである事を示す名称をつけました。この名称は、プロジェクトにおいて一意な名称をつける必要があります(つまり、ここで再び db1.0 と指定するとエラーとなります)。

f:id:daigo-knowlbo:20161108020910p:plain

データベース上のテーブル定義を確認すると、以下のように EmployeeテーブルにBirthカラムが追加された事を確認する事ができます。

また、__EFMigrationsHistoryテーブルの内容を確認すると、db1.0 → db1.1 と2段階のMigrationが行われた事を確認する事ができます。

f:id:daigo-knowlbo:20161108023518p:plain

では、またー。