読者です 読者をやめる 読者になる 読者になる

チーム開発のための .NET 開発環境の整備(DBマイグレーション)

.NETでのチーム開発のための環境整備を社内で作成しているところですが、備忘もかねて作業記録や気が付いた点など書いていきます。


今回はデータベースのMigrationについて記載します。

今回の概要

  1. Migrationでスキーマの履歴を管理する

今回の環境

  • VisualStudio 2012
  • Entity Framework 5.0
  • ASP.NET MVC4

1. Migrationでスキーマの履歴を管理する

MigrationはEntityFramework4.3で採用された機能になります。前回の記事では、起動時のInitializerにDropCreateDatabaseIfModelChangesを指定することでモデルクラスで変更したコードに対してスキーマの同期をとるようにしていましたが、最新の状態のみ管理することになります。これに対してMigrationを使用するとスキーマーの状態の履歴をトラッキングすることができ、さらにMigrationのタイミングでのデータ投入、Index作成、Default値設定などを行うことが可能になります。

インストール

パッケージマネージャコンソールでEnable-Migrationsを実行します。

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

既にモデルがある場合は、実行後にテーブルの初期生成のマイグレーションクラスが Migrationsフォルダの下に作成されます。

namespace MiniBlogSample.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class InitialCreate : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "dbo.Messages",
                c => new
                    {
                        MessageID = c.Int(nullable: false, identity: true),
                        Text = c.String(),
                        User = c.String(),
                        CreatedAt = c.DateTime(nullable: false),
                        UpdatedAt = c.DateTime(nullable: false),
                    })
                .PrimaryKey(t => t.MessageID);
            
        }
        
        public override void Down()
        {
            DropTable("dbo.Messages");
        }
    }
}

Upメソッドは、変更をする際に必要なメソッドを記述して、Downメソッドにはその変更を元に戻す際に必要なメソッドを記述します。

また同じMigrationsフォルダにConfigration.csが作成されます。 前回作成した初期データ投入のためのSeed()の中身をこちらのソースに移動します。

namespace MiniBlogSample.Migrations
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<MiniBlogSample.Models.MiniBlogSampleDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(MiniBlogSample.Models.MiniBlogSampleDbContext context)
        {
            context.Messages.AddOrUpdate(
            new MiniBlogSample.Models.Message
            {
                Text = "Dummy_Text",
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            });
        }
    }
}

今回は自動Migrationを行わないので

AutomaticMigrationsEnabled = false;

としておきます。

AutomaticMigrationsEnabled = true;

としておくと、Add-Migrationを実行してMigrationファイルを作成しなくても モデルクラスと現在のスキーマを比較して変更点があれば自動的にマイグレーションしてくれます。

最後にGlobal.asax.csのSetInitializerをMigrationを使用するように変更します。

Database.SetInitializer(new MigrateDatabaseToLatestVersion<MiniBlogSampleDbContext, Configuration>());
Migrationの実行
namespace MiniBlogSample.Models
{
    public class Message
    {
        public int MessageID { get; set; }
        public string Text { get; set; }
        public string User { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
    }
}

に対して、Userカラムを削除します

namespace MiniBlogSample.Models
{
    public class Message
    {
        public int MessageID { get; set; }
        public string Text { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
    }
}

修正したモデルのコードを保存した後、パッケージマネージャーコンソールで Add-Migrationを実行します。

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

実行すると、MigrationsフォルダにMigrationのソースが作成されます。

  public partial class RemoveColumnUserFromMessage : DbMigration
    {
        public override void Up()
        {
            DropColumn("dbo.Messages", "User");
        }
        
        public override void Down()
        {
            AddColumn("dbo.Messages", "User", c => c.String());
        }
    }

Initializerで設定しているためアプリケーション起動してDbContextにアクセスしたときに自動でMigrationが起動されますが、 手動でも実行することができます。その際はパッケージコンソールマネージャからUpdate-Databaseを実行します。 オプション -Verboseを付けると実行するSQL文を表示し確認することができます。

PM> Update-Database -Verbose
Using StartUp project 'MiniBlogSample'.
Using NuGet project 'MiniBlogSample'.
ターゲット データベースに適用されている SQL ステートメントを表示するには、'-Verbose' フラグを指定します。
ターゲット データベースは 'MiniBlogSampleDb' (データソース: (LocalDb)\v11.0、プロバイダー: System.Data.SqlClient、原点: Configuration) です。
コードベースの移行を適用しています: [201308011029266_InitialCreate, 201308060627006_RemoveColumnUserFromMessage]。
コードベースの移行を適用しています: 201308011029266_InitialCreate。
CREATE TABLE [dbo].[Messages] (
    [MessageID] [int] NOT NULL IDENTITY,
    [Text] [nvarchar](max),
    [User] [nvarchar](max),
    [CreatedAt] [datetime] NOT NULL,
    [UpdatedAt] [datetime] NOT NULL,
    CONSTRAINT [PK_dbo.Messages] PRIMARY KEY ([MessageID])
)
CREATE TABLE [dbo].[__MigrationHistory] (
    [MigrationId] [nvarchar](255) NOT NULL,
    [Model] [varbinary](max) NOT NULL,
    [ProductVersion] [nvarchar](32) NOT NULL,
    CONSTRAINT [PK_dbo.__MigrationHistory] PRIMARY KEY ([MigrationId])
)
BEGIN TRY
    EXEC sp_MS_marksystemobject 'dbo.__MigrationHistory'
END TRY
BEGIN CATCH
END CATCH
[移行履歴レコードの挿入]
コードベースの移行を適用しています: 201308060627006_RemoveColumnUserFromMessage。
DECLARE @var0 nvarchar(128)
SELECT @var0 = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'dbo.Messages')
AND col_name(parent_object_id, parent_column_id) = 'User';
IF @var0 IS NOT NULL
    EXECUTE('ALTER TABLE [dbo].[Messages] DROP CONSTRAINT ' + @var0)
ALTER TABLE [dbo].[Messages] DROP COLUMN [User]
[移行履歴レコードの挿入]
Seed メソッドを実行しています。
PM> 

マイグレーションを最初からやり直したい場合は

PM> Update-Database -Force -TargetMigration:"0"

とやってから、

PM> Update-Database -Force

とします。