かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Entity Framework CodeFirst CTP5のDBの初期化方法

CodeFirstで定義した情報は、勝手にDBに定義してくれました。これって変更できるのだろうか確認してみたら以下のメソッドを使って変更できるみたいです。

  • System.Data.Entity.Database.DbDatabaseクラス
    • SetInitializer(IDatabaseInitializer strategy)

このSetInitializerメソッドのIDatabaseInitializerに何を渡すかでどう動くのか決まるみたいです。とりあえず見つけたIDatabaseInitializerの派生クラスには以下のようなものがありました。

  • System.Data.Entity.Database.CreateDatabaseIfNotExists
  • System.Data.Entity.Database.DropCreateDatabaseAlways
  • System.Data.Entity.Database.DropCreateDatabaseIfModelChanges

名前からして何をするのかわかりやすいと思いますが、上からDBが無ければ生成、常に削除して作り直し、もし変更があったら作り直しの動きをします。デフォルトがどれかは、わからなかったのですが、動きを見ている限り毎回消してもくれないし、クラスの定義を試しに変えてみてもDBの作り直しがおこらなかったのでCreateDatabaseIfNotExistsになってると思われます。

試しに、前に作ったサンプルのDepartmentクラスにプロパティを追加します。

public class Department
{
    public int ID { get; set; }

    public string Name { get; set; }

    // 追加
    public bool IsAdmin { get; set; }

    public virtual ICollection<Employee> Employees { get; set; }
}

その状態で実行すると、以下のような例外が表示されます。

ハンドルされていない例外: System.InvalidOperationException: The model backing the 'EduContext' context has changed since the database was created. Either manually delete/update the database, or call Database.SetInitializer with an IDatabaseInitializer instance. For example, the DropCreateDatabaseIfModelChanges strategy
 will automatically delete and recreate the database, and optionally seed it with new data.
   場所 System.Data.Entity.Database.CreateDatabaseIfNotExists`1.InitializeDataba

DB変わってるからダメよと。手動でDB更新するかSetInitializer使って挙動変えてねってことらしい。なので、Mainメソッドに以下の行を追加します。

static void Main(string[] args)
{
    // DBに変更があったら再生成してね!
    DbDatabase.SetInitializer(
        new DropCreateDatabaseIfModelChanges<EduContext>());

    int deptId;

    using (var ctx = new EduContext())
    {
        // Create
        {

この状態で実行するとエラーが出ないで実行が完了します。DBを見ると以下のようにテーブルに列が追加されているのがわかります。

さらに、このSetInitializerは自分でカスタマイズしたものを追加することが出来ます。1から作るのは大変なので既存のものを継承して作るのがお手軽です。ここで、DB作成の後に初期データを突っ込んだりとかすると吉です。とりあえずDropCreateDatabaseIfModelChangesにはSeedメソッドがあって、こいつをオーバーライドして初期データを突っ込む処理を書けばOKみたいです。さっそく実験してみました。

namespace HelloCodeFirst
{
    using System.Data.Entity.Database;

    public class CustomDropCreate : DropCreateDatabaseIfModelChanges<EduContext>
    {
        protected override void Seed(EduContext context)
        {
            base.Seed(context);
            // 必須データを作っておく
            var dept = new Department { Name = "社長室", IsAdmin = true };
            context.Departments.Add(dept);
        }
    }
}

そして、Mainの最初のSetInitializerをCustomDropCreateを使うように書き換えます。

// カスタム再生成ロジックにする
DbDatabase.SetInitializer(
    new CustomDropCreate());

このまま実行してもエンテティに変更がないのでDBの再生成が走らないのでDBを削除してからプログラムを再実行します。実行結果はいつも通りなので割愛して、DBのほうを覗いてみます。Departmentテーブルのデータを抽出してみると・・・

ID Name IsAdmin
1 社長室 True

このようにCustomDropCreateで作成したデータが入ってることがわかります。


さて、この機能ですが単体テストのTestInitializeAttributeで実行されるテスト単位の初期化処理とかでDBを作り直すようにセットしておくと吉かもしれません。テスト用データとかを突っ込む処理も入れておけば完璧っぽいです。
あまりDBにつなぐテストはしたくないですが、しなきゃいけないときには使えそうな機能です。