しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC 3 開発入門 (3) - モデルをコードファーストで作成

ASP.NET MVC 3 開発入門 - インデックス

2011/03/20 - Entity Framework 4.1 向けに修正

実は ASP.NET MVC は名前に反して M のモデルに相当する部分は提供されていませんので、LINQ to SQL や Entity Framework などを別で選択する必要があります。今回は Entity Framework CTP 5 4.1 を使ってコードファーストでモデルクラスの開発を行っていきましょう。

そのままでは Entity Framework を利用することができないので、まずは Entity Framework CTP 5 4.1 を参照設定に加えます。ソリューション エクスプローラの「参照設定」を右クリックして表示されるメニューから「参照の追加」を選択し、表示されるダイアログから Entity Framework を選択し OK をクリックすると参照に追加されます。

ここに Entity Framework が表示されていない場合は Entity Framework CTP 5 4.1 がインストールされていませんのでインストールする必要があります。Entity Framework CTP 5 4.1 を参照に追加する方法は手動で追加する以外にも MVC 3 と同時にインストールされる NuGet を使う方法があります。

NuGet を使ったインストール方法にはコンソールを使う方法と GUI を使う方法の2種類あります。NuGet のメニューは「ツール」内に「Library Package Manager」サブメニューとして用意されています。

GUI からのインストールは NuGet メニュー内の「Add Library Package Reference」をクリックするとダイアログが表示されるので、サイドバーから「Online」を選択して検索ボックスにインストールしたいパッケージ名を入力すると検索結果が表示されます。ここでは "EFCodeFirst""EntityFramework" と入力しましょう。

後はインストールしたい項目を選択して「Install」をクリックすれば自動的にダウンロードと参照の追加が行われて、利用可能な状態なります。

コンソールからのインストールも GUI からのインストールと同様に、NuGet メニュー内の「Package Manager Console」をクリックするとコンソールが表示されます。

表示されたコンソールに

//Install-Package EFCodeFirst
Install-Package EntityFramework

と入力すれば自動的にダウンロードと参照の追加が行われて利用可能な状態になります。

これで Entity Framework を使用する準備が出来ましたので、次はモデルクラスを作成していきましょう。専用のテンプレートが用意されているわけではないので、ソリューション エクスプローラから Models フォルダを右クリックし、「追加」→「クラス…」をクリックして新しいクラスを追加します。

まずは動画情報のモデルクラスを作成するので、表示されたダイアログのファイル名に Video と入力して「OK」をクリックします。するとModels フォルダに Video.cs ファイルが追加されて、開くと中には最低限のクラス定義が書かれていることがわかると思います。

Entity Framework を触ったことのある方なら EDM (Entity Data Model) を作らないことを疑問に思われると思いますが、今回はコードファースト開発を行いますので俗に言う POCO (Plain-Old CLR Object) でエンティティを作成します。

Video クラスの実装ですが、特にロジックを書くことはしません。代わりにプロパティとデータアノテーション属性の追加を行います。通常ではクラスはテーブルに、プロパティは同名のカラムにマッピングされます。Entity Framework のコードファースト開発ではプロパティ名に規約が定められているので、それの規約にしたがってプロパティを実装するだけで適切なテーブルを作成することが出来ます。

以下に基本的なプロパティ名の規約を紹介します。

  • 主キー
    • Id, ClassName + Id
    • 例:VideoId
  • 外部キー
    • OtherClassName + Id
    • 例:UserId
  • ナビゲーションプロパティ
    • OtherClassName
    • 例:Comments

基本的な用途ではこれで十分だと思われますが、既存の DB へのモデルクラスを作成する時など、明示的に指定を行いたい場合には Key 属性や Fluent API を使うことでより細かく設定を行えます。

ここで注意する必要しなくてはならないのが、ナビゲーションプロパティを正常に動作させるためにはプロパティ定義に virtual を付ける必要があるという点です。これは Entity Framework が POCO エンティティを継承したプロキシクラスを自動的に作成するからです。プロキシクラスを作ってくれているので、ナビゲーションプロパティの遅延読み込みや変更追跡を使うことが出来ています。

プロパティの追加が出来たら次はデータアノテーション属性を使って、プロパティの値に対して制約、検証を行うことを指定します。以下で一部ですがよく使われる属性を紹介します。

  • Required
    • 必須であることを指定します
  • StringLength
    • 文字列の最大長、最少長を指定します
  • Range
    • 値の最小値、最大値を指定します
  • DisplayName
    • プロパティの表示名を指定します
    • この属性で定義した名前は Html.LabelFor などのヘルパーで自動的に使用されます

こうして作成した Video クラスは以下のようになります。

public class Video
{
    public int VideoId { get; set; }

    [Required]
    [StringLength(64)]
    [DisplayName("タイトル")]
    public string Title { get; set; }

    [DisplayName("説明")]
    public string Description { get; set; }

    public DateTime CreatedAt { get; set; }

    public DateTime UpdatedAt { get; set; }

    public virtual ICollection<Comment> Comments { get; set; }

    public virtual ICollection<Tag> Tags { get; set; }
}

プロパティ名の規約で述べたように VideoId プロパティがプライマリキーとなります。Title プロパティは動画のタイトルを保存するので必須かつ最大長を 64 文字に制限しました。

気になるのは Comments と Tags プロパティが ICollection で定義されている点だと思いますが、Entity Framework では1対多関係などで複数のエンティティに関連づいていることを表現するときにコレクション型を使います。コレクション型を使うと Entity Framework は自動的に必要な外部キーを内部的に作成して、適切なテーブルを作成してくれます。

コレクションですので値の追加や削除をするときには普段通り Add、Remove メソッドを呼び出すだけです。後ろにあるデータベースを殆ど意識せずに、通常のクラスと同様に扱える点もコードファースト開発での利点だと言えます。

それでは同様に残りの Comment と Tag クラスも作成しましょう。基本は Video クラスと変わりません。

Comment クラス

public class Comment
{
    public int CommentId { get; set; }

    [Required]
    [StringLength(64)]
    [DisplayName("名前")]
    public string Name { get; set; }

    [Required]
    [StringLength(1024)]
    [DisplayName("本文")]
    public string Body { get; set; }

    public DateTime CreatedAt { get; set; }
}

Tag クラス

public class Tag
{
    public int TagId { get; set; }

    [Required]
    [StringLength(32)]
    [DisplayName("タグ名")]
    public string Name { get; set; }

    public virtual ICollection<Video> Videos { get; set; }
}

注目すべきは Tag クラスのコレクション型である Videos プロパティです。Video クラスにも Tags プロパティがあるのでお互いに複数のエンティティを参照する形になっています。今回の Video と Tag は多対多関係とするためにこのようにコレクション型で定義しているのですが、Entity Framework はこのようなプロパティを発見すると自動的に中間テーブルを作成して、すべて自動で多対多関係を作成してくれます。

これで必要なモデルクラスが全て揃いましたので、最後はこのクラスを実際に使えるようにするためのデータコンテキストを作りましょう。Entity Framework CTP 5 4.1 には DbContext というクラスが用意されているので、これを継承して MvcVideoContext クラスを作ります。

この MvcVideoContext クラスには DbSet 型のプロパティを追加する必要があります。追加したプロパティは同名のテーブルとしてマッピングが行われて自動的にインスタンス化されるので、以降はそのインスタンスに対してメソッド呼び出しや LINQ を利用してエンティティの追加、削除、検索などが行えます。

public class MvcVideoContext : DbContext
{
    public DbSet<Comment> Comments { get; set; }
    public DbSet<Tag> Tags { get; set; }
    public DbSet<Video> Videos { get; set; }
}

このクラスでは Comments, Tags, Videos という 3 つのテーブルが自動的に生成されます。基本的にはこのクラスをインスタンス化すれば使えるようになるのですが、その前にデータベースへの接続文字列を Web.config に追加しましょう。

今回は SQL Server Compact Edition 4.0 を利用します。データベースファイルのパスを接続文字列として指定する必要があるので、以下のようなタグを Web.config の connectionStrings 要素内に追記します。

<connectionStrings>
  <add name="MvcVideoContext"
       connectionString="Data Source=|DataDirectory|MvcVideo.sdf"
       providerName="System.Data.SqlServerCe.4.0"/>
</connectionStrings>

気を付ける必要があるのは name には DbContext を継承したクラスと同じ名前を指定しないといけない点です。実際にはデフォルトがクラス名になっているだけで、DbContext のコンストラクタで指定できるのですが、特に変える必要はないと思うのでデフォルトのままでいきましょう。ちなみに connectionString 属性内の Data Source で指定している |DataDirectory| は App_Data フォルダを指定するキーワードで、今回は App_Data フォルダ内に MvcVideo.sdf というファイルが作られることになります。

追記(2011/02/15)

大事なことを忘れていました。

必要なモデルクラスとデータコンテキスト、そして接続文字列の準備は出来ましたが、データベースの自動生成を使うためには Global.asax.cs の Application_Start メソッドなどで DbDatabase.SetInitializer Database.SetInitializer メソッドを呼び出して設定する必要があります。

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    // この一行を追加!
    // Entity Framework CTP 5 での書き方
    //DbDatabase.SetInitializer(new DropCreateDatabaseIfModelChanges<MvcVideoContext>());
    Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MvcVideoContext>());
}

SetInitializer メソッドに DropCreateDatabaseIfModelChanges クラスのインスタンスを指定しています。このクラスはモデルクラスに何らかの変化があれば自動的にテーブルを作り直してくれるという、開発中には非常に便利な機能を持っています。このクラス以外にも

  • CreateDatabaseIfNotExists
    • データベースが存在しない場合には作成
  • DropCreateDatabaseAlways
    • 常にデータベースを生成しなおす

などが予め用意されています。継承したクラスを実装すると、データベースの初期化時に初期データを追加することなども出来ます。

これで Entity Framework を使ったコードファーストでのモデル作成は完了しました。次回はリポジトリパターンを適用し、データアクセスを抽象化してテストを行いやすい形にしていきましょう。