なか日記

一度きりの人生、楽しく生きよう。

Entity Frameworkでデータベースマイグレーションしてみる

Entity Frameworkを使用してコードファーストな開発ができますよね。データベース構造とモデル(クラス)の両方を考えなくていいのでちょっとしたアプリを作るときには便利だと思います。*1

私が初めてEntity Frameworkを触った頃は以下の様な感じで

Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyDbContext>());
  • データベースが存在しない場合にのみ作成する
  • モデルに変更があった際に作り直す
  • 常に作り直す

しかなかった気がします。

なので前々から「モデルに変更があった場合どうやってデータベースの構造を変更するんだろう?」という疑問を持っていました。

そんな中、「チーム開発実践入門 ~共同作業を円滑に行うツール・メソッド (WEB+DB PRESS plus)」を読んでRailsにはマイグレーションの機能があることを知りました。そこで、Entity Frameworkはどうなんだろう?と調べてみたところ、以下のサイトでマイグレーションの仕組みがあることを知り、概要を確認してホッとした所で止まってました。

目次

試さざるを得ない状況が発生

受付アプリ公開してデータを登録してもらったので、データベースの変更でせっかく登録したデータが消えちゃうのは切なすぎます。従って必然的にマイグレーションを使う必要性が出てきます。

そしてさっそく使わざるを得ない状況(バグがあった)が発生しました(;´Д`)

マイグレーションを使えるようにする

Entity Framework使えるようにしてるだけではマイグレーションの機能は使えないので、パッケージマネージャーコンソールで以下のコマンドを実行します。

PM> Enable-Migrations
コンテキストが既存のデータベースを対象にしているかをチェックしています...
データベース初期化子で作成されたデータベースが検出されました。既存のデータベースに対応する移行 '201406091801115_InitialCreate' がスキャフォールディングされました。代わりに自動移行を使用するには、Migrations フォルダーを削除し、-EnableAutomaticMigrations パラメーターを指定して Enable-Migrations を再実行します。
Code First Migrations がプロジェクト ReceptionDesk で有効になりました。

すると、Migrationsフォルダが作成され、その中に以下のようなファイルが作成されます。

  • 201406091801115_InitialCreate.cs
  • 201406091801115_InitialCreate.Designer.cs
  • 201406091801115_InitialCreate.resx

モデルを変更する

次にモデルを変更します。

今回はParticipantsモデルのStudyMeetingメンバーにRequired属性を付けました。

    [Required]
    public virtual StudyMeeting StudyMeeting { get; set; }

それからパッケージマネージャーコンソールで以下のコマンドを実行します。

PM> Add-Migration AddRequiredAttributeToStudyMeetingOnParticipant
移行 'AddRequiredAttributeToStudyMeetingOnParticipant' をスキャフォールディングしています。
この移行ファイルのデザイン コードには、現在の Code First モデルのスナップショットが含まれています。このスナップショットは次の移行をスキャフォールディングする際、モデルに対する変更の計算に使用されます。モデルに追加の変更を行い、この移行に含める場合は、'Add-Migration AddRequiredAttributeToStudyMeetingOnParticipant' を再実行して再度スキャフォールディングできます。

すると、Migrationsフォルダに以下の様なファイルが作成されます。

  • 201406111744470_AddRequiredAttributeToStudyMeetingOnParticipant.cs
  • 201406111744470_AddRequiredAttributeToStudyMeetingOnParticipant.Designer.cs
  • 201406111744470_AddRequiredAttributeToStudyMeetingOnParticipant.resx

201406111744470_AddRequiredAttributeToStudyMeetingOnParticipant.csを見てみると以下の様に外部キーの削除、制約の付与、外部キーの作成といった流れの処理が書かれています。

    public partial class AddRequiredAttributeToStudyMeetingOnParticipant : DbMigration
    {
        public override void Up()
        {
            DropForeignKey("dbo.Participants", "StudyMeeting_Id", "dbo.StudyMeetings");
            DropIndex("dbo.Participants", new[] { "StudyMeeting_Id" });
            AlterColumn("dbo.Participants", "StudyMeeting_Id", c => c.Int(nullable: false));
            CreateIndex("dbo.Participants", "StudyMeeting_Id");
            AddForeignKey("dbo.Participants", "StudyMeeting_Id", "dbo.StudyMeetings", "Id", cascadeDelete: true);
        }
        
        public override void Down()
        {
            DropForeignKey("dbo.Participants", "StudyMeeting_Id", "dbo.StudyMeetings");
            DropIndex("dbo.Participants", new[] { "StudyMeeting_Id" });
            AlterColumn("dbo.Participants", "StudyMeeting_Id", c => c.Int());
            CreateIndex("dbo.Participants", "StudyMeeting_Id");
            AddForeignKey("dbo.Participants", "StudyMeeting_Id", "dbo.StudyMeetings", "Id");
        }
    }

データベースのアップデート

パッケージマネージャーコンソールで以下のコマンドを実行するだけです。簡単ですね。

PM> Update-Database –Verbose
ターゲット データベースに適用されている SQL ステートメントを表示するには、'-Verbose' フラグを指定します。
明示的な移行を適用しています: [201406111744470_AddRequiredAttributeToStudyMeetingOnParticipant]。
明示的な移行を適用しています: 201406111744470_AddRequiredAttributeToStudyMeetingOnParticipant。
Seed メソッドを実行しています。

–Verboseオプションを指定すると実行されたSQLを確認することができます。

AzureのSQLデータベースの場合

ローカルの場合はパッケージマネージャーコンソールでできたけど、AzureのSQLデータベースに対してはどうするんだろう?と思いましたが、同じくパッケージマネージャーコンソールからできました。

PM> Update-Database -StartUpProjectName "ReseptionDesk" -ConnectionString "Server=tcp:{サーバのアドレス},1433;Database={データベース名};User ID={ユーザ名};Password={パスワード};Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" -ConnectionProviderName "System.Data.SqlClient"

ポイントとしては以下の通りです。

  1. オプションで接続文字列や接続プロバイダを指定する必要がある
  2. Azure側のファイアウォールに穴を開けておく必要がある

接続文字列

Azureのダッシュボードにある「接続文字列の表示」を使用するのが楽でいいと思います。

f:id:nakaji999:20140613010106p:plain

ファイアウォールの設定

ダッシュボードの「使用できるIPアドレスの管理」から行います。

「現在のクライアント IP アドレス」が表示されていると思いますので、そのまま「使用できるIPアドレスに追加します」を実行すればOKです。

自動マイグレーション

上記の手順はパッケージマネージャーコンソールから明示的にマイグレーションファイルを作成しますが、これを自動化することもできます。

やり方は以下の様にEnable-Migrationsする際-EnableAutomaticMigrations オプションを付けて実行するだけです。

PM> Enable-Migrations -EnableAutomaticMigrations 
コンテキストが既存のデータベースを対象にしているかをチェックしています...
Code First Migrations がプロジェクト ReceptionDesk で有効になりました。

モデルを変更したらそのままUpdate-Databaseの実行でマイグレーションが行われます。

PM> Update-Database –Verbose

既にEnable-Migrationsしてた場合

最初にEnable-Migrationsしたときのメッセージに書かれていますが、Migrationsフォルダを削除後、-EnableAutomaticMigrationsオプション付きでEnable-Migrationsを実行すれば良さそうですが、私の環境ではテーブルを再作成しようとして以下のエラーになってしまいました。

System.Data.SqlClient.SqlException (0x80131904): There is already an object named 'CheckInHistories' in the database.

テーブルを全て削除した後、Update-Databaseしてテーブルを作成すればそれ以降は自動マイグレーションできそうな感じですが、ちょっと面倒ですね。(エクスポート&インポートすればいいだけではありますが・・・)

おまけ

対象のデータベースにどこまでのマイグレーションが適用されているのか、どうやって把握していんだろう?と思いましたが、データベース上に__MigrationHistoryというテーブルが作成されいてました。なるほど。

f:id:nakaji999:20140613015456p:plain

まとめ

簡単な変更であればコマンドを数回叩くだけでいいのでかなり楽ですね。これで不安は解消されたので、オレオレプロジェクトでは積極的にコードファーストしていきたいと思います。

Entity Framework 6 Recipes

Entity Framework 6 Recipes

*1:大規模なアプリでも使えるとは思いますがやったことない