WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

MVC4 EF Code First

by WebSurfer 20. December 2014 20:41

今さらながらですが、ASP.NET MVC ベースの Web アプリケーションを開発する際、Entity Framework の Code First の機能を使って SQL Server データベースを生成してみましたので、その話を備忘録として書いておきます。

生成した DB 内容の表示

基本的には @IT の記事「第2回 Entity Frameworkコード・ファーストでモデル開発」に書いてあった通りの方法で作成してみました。

ただし、環境は MVC3 ⇒ MVC4、EF4 ⇒ EF6、SQL Server Compact 4.0 ⇒ SQL Server 2008 Express と異なります。

また、作成するテーブルも少し簡略化しました。(もちろん @IT の記事の通りに SQL Server にテーブルを作成できます。簡略化したのは、後日、今回作成したデータベースを使って別の記事を書くためです)

Visual Studio 2010 への MVC4 のインストール方法、ベースとなる MVC4 のインターネットアプリケーションの説明については以下のページを見てください。

Entity Framework Code First の機能を使えば、結果として生成されるデータベースのスキーマ等は考える必要はなくなるというような話を聞きますが、個人的には、そんなことはないと思ってます。

単一のテーブルならそうかもしれませんが、今回説明する例のように、Code First の機能によって 2 つのテーブルを生成し、それに外部キー制約がついているような場合は 階層更新 を考えなければなりません。

まぁ、それは次の記事の話として書くとして、今回は Code First の機能を利用して、外部キー制約のある 2 つのテーブルを作るところまでを書きます。

Model

まず、テーブル生成の大元になる Model ですが、以下の例を考えます。親子関係にある Parent クラス と Child クラスおよび ParentChildContext クラスに注目してください。ParentChildInitializer クラスは初期化のためのもの(イニシャライザ)です。

ParentChildContext クラスに設定されている接続文字列名 "DefaultConnection" は、Visual Studio の「MVC4 のインターネットアプリケーション」テンプレートでアプリケーションを作成すると自動的に web.config に設定されるものです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Web.Mvc;
using System.Data.Entity;

namespace Mvc4App2.Models
{
  public class ParentChildInitializer : 
      DropCreateDatabaseIfModelChanges<ParentChildContext>
  {
    protected override void Seed(ParentChildContext context)
    {
      // とりあえずレコードを 2 つ作成してみた。
      // Id は IDENTITY になるので設定不用。
      var parents = new List<Parent> {
          new Parent { Name = "親の名1" },
          new Parent { Name = "親の名2" },
      };

      parents.ForEach(p => context.Parents.Add(p));
      context.SaveChanges();
    }
  }    
    
  public class ParentChildContext : DbContext
  {
    // 接続文字列名 DefaultConnection を指定。
    // 指定しないとクラス名 ParentChildContext
    // で探しにいくので注意。
    public ParentChildContext()
          : base("DefaultConnection")
    {
    }

    public DbSet<Parent> Parents { get; set; }
    public DbSet<Child> Children { get; set; }
  } 
    
  public class Parent
  {
    public Parent()
    {
      Children = new List<Child>();
    }

    public int Id { get; set; }

    [Required(ErrorMessage = "{0} は必須")]
    [StringLength(5, ErrorMessage = "{0} は {1} 文字以内")]
    [Display(Name = "Parent Name")]
    public string Name { get; set; }

    public virtual IList<Child> Children { get; set; }
  }

  public class Child
  {
    public int Id { get; set; }

    [Required(ErrorMessage = "{0} は必須")]
    [StringLength(5, ErrorMessage = "{0} は {1} 文字以内")]
    [Display(Name = "Child Name")]
    public string Name { get; set; }
  }
}

ParentChildContext クラスに定義されている Parents, Children プロパティが DB 上のテーブルに、Parent クラス と Child クラスがそれぞれのテーブル内のレコードに該当します。

Parent, Child クラスに定義されている Id という名前のプロパティが DB 上のテーブルでは主キーとして設定されます。int 型の場合は自動的に IDENTITY になりますので注意してください。

また、Name という名前のプロパティは、プロパティに付与した型 string と属性 Required(...), StringLength(5, ...) の設定により、DB のテーブルには Name という名前の nvarchar(5)、NULL 不許可のフィールドとして生成されます。 (参考にした @IT の記事に "null を許容する値型の列は、null 許容型で指定する" と書いてあったんですが、Required(...) 属性の設定が優先されるようです)

特に重要なのが、Parent クラスの中で virtual として宣言されている Chidren プロパティです。

これは「ナビゲーションプロパティ」と呼ばれるもので、上に紹介した @IT の記事によると "エンティティ上で virtual キーワードの付いたプロパティは、それが遅延ロードされることを表す。つまり、明示的に該当するプロパティにアクセスするまで、参照先の値はデータベースから取得されない" というものだそうです。

逆に言えば、この記事の例では、プログラムで Parent クラスのナビゲーションプロパティ Children にアクセスすれば、データベースの Children テーブルから関連するレコードを取得できるということになります。

上記の Model をベースに Entity Framework Code First の機能を利用して SQL Server のデータベースに生成されるテーブルの構成は以下の画像(SQL Server Managemant Studio で見たもの)に示すとおりです。

生成され���テーブル

aspnet-Mvc4App2-20140721201705 というデータベースは、Visual Studio の「MVC4 のインターネットアプリケーション」テンプレートでアプリケーションを作成して、ユーザー登録すると自動的に作成されるものです。(開発環境に SQL Server Express がインストールされている場合)

Children テーブルの Parent_Id という外部キーフィールドは、Parent クラスに Children というナビゲーションプロパティを定義したことによって、フレームワークが自動的に作った NULL を許容する外部キーフィールドです。Parents テーブルの Id フィールドと外部キー制約を持っています。

この記事の Child クラスには外部キープロパティを定義していませんが、Microsoft の文書「Code First の規約」よると、依存オブジェクトを表す型(この記事の例では Child クラス)には外部キープロパティを含めることをお勧めしますとのことです。理由は、連鎖削除を設定するか否かをコントロールするためのようです。

2016/9/12 追記:
Microsoft の文書「Code First の規約」に従って、Child クラスにナビゲーションプロパティと外部キープロパティを定義するとどのような影響があるかを別の記事「Code First で外部キープロパティの定義」に書きました。外部キープロパティを int 型にしたので、外部キーフィールドはこの記事と違って NULL 不可になります。詳しくは記事を見てください。

「親の名1」「親の名2」という 2 つのフィールドは、ParentChildInitializer クラスの Seed メソッドにより初期値として設定されたものです。

Global.asax

イニシャライザ ParentChildInitializer クラスは、Global.asax の Application_Start メソッドでアプリケーションに登録します。具体例は以下の通りです。

public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    AreaRegistration.RegisterAllAreas();

    // ・・・中略・・・

    Database.SetInitializer<ParentChildContext>(
                           new ParentChildInitializer()); 
  }
}

ここまで済んだら、Visual Studio の[ビルド(B)]⇒[ソリューションのビルド(B)]でソリューションをビルドしておきましょう。そうしておかないと、以降の手順でモデル・クラスなどが認識されません。

実は、ここまででは、まだ SQL Server の DB にはテーブルは作成されていません。Controller と View を作って、Controller のアクションメソッドを呼び出す必要があります。

Controller と View はスキャフォールディング機能で自動的に生成できます(詳しくは上に紹介した @IT の記事を見てください)。以下に、一覧を表示する Index の部分のみ書いておきます。

Controller

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Mvc4App2.Models;

namespace Mvc4App2.Controllers
{
  public class ParentChildController : Controller
  {
    private ParentChildContext db = new ParentChildContext();

    //
    // GET: /ParentChild/

    public ActionResult Index()
    {
      return View(db.Parents.ToList());
    }

    // ・・・中略・・・

  }
}

View

@model IEnumerable<Mvc4App2.Models.Parent>

@{
  ViewBag.Title = "Index";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Index</h2>
<p>
  @Html.ActionLink("Create New", "Create")
</p>
<table>
  <tr>
    <th>
      @Html.DisplayNameFor(model => model.Id)
    </th>
    <th>
      @Html.DisplayNameFor(model => model.Name)
    </th>
        
    <th></th>
  </tr>

  @foreach (var item in Model) {
    <tr>
      <td>
        @Html.DisplayFor(modelItem => item.Id)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Name)
      </td>
      <td>
        @Html.ActionLink("Edit", "Edit", 
            new { id=item.Id }) |
        @Html.ActionLink("Details", "Details", 
            new { id=item.Id }) |
        @Html.ActionLink("Delete", "Delete", 
            new { id=item.Id })
      </td>
    </tr>
  }
</table>

一番上の画像が、上記の Controller と View を使って表示したものです。

Tags:

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  January 2020  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar