Archive

Archive for the ‘EF 4.1 Code First’ Category

コードファーストでSQL Serverを利用する接続文字列

コードファーストの説明ではこのエントリのようにSQL CE 4.0を利用するものが多いです。
雑誌の記事を書くにあたってSQL Serverを利用する方法を試行錯誤したのでまとめておきます。

1.接続文字列を書かない

接続文字列を記述していない場合は、デフォルトでSQL Server Expressのインスタンスが利用されます。
SQL Server Expressがインストールされていないマシンではエラーが発生すると思われます(未確認)。
このとき、データベース名はDbContextを継承したクラスのnamespaceを含んだ名前になります。
例えば"MvcApplication13.Models.MemberDB"というデータベースが作られたりします。

2.データベース名を指定するには

データベース名を指定するにはDbContextを継承したクラスと同じ名前の接続文字列を追加します。
このときInitial Catalogで指定した名前のデータベースが、指定したSQL Serverのインスタンスの中に作られます。
次のような記述であれば、MemberDBという名前のデータベースが作成されます。

例:
    <add name="MemberDB"
         connectionString="data Source=.\SQLEXPRESS;Integrated Security=SSPI;Initial Catalog=MemberDB;"
         providerName="System.Data.SqlClient" />

 

3.ユーザーインスタンスを利用するには

一般的な開発ではSQL Server Express特有の機能であるユーザーインスタンスを利用して、データベースのmdfファイルをプロジェクトの内部に作成して利用しています。
このように作成するデータベースのmdfファイルをプロジェクトの中に含めたい場合、ユーザーインスタンスを利用するための設定とmdfファイルの配置場所を設定する必要があります。
これは次のような記述で実現できます。

例:
    <add name="MemberDB"
         connectionString="data Source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDbFilename=|DataDirectory|MemberDB.mdf;User Instance=True;Initial Catalog=MemberDB;"
         providerName="System.Data.SqlClient" />

この場合mdfファイルが作られても最初はソリューションエクスプローラの中に表示されませんが、すべてのファイルを表示させてmdfファイルをプロジェクトに含めれば、あとは通常の開発時と同様に扱えます。

ただし、ユーザーインスタンスを使っているときに注意が必要な点が一つあります。

  『データモデルを変更したからといってmdfファイルを削除しないこと』

適切なInitializerの設定をしていれば、モデルの変更時にデータベースを自動的に作り変えてくれます。
この機能を使わなくてもmdfファイルを消せば新しくデータベースが作られるのではないか、と思ってしまいがちですがそんなことはありません。
むしろよくわからないエラーがでてきて対処に困る、という状況におちいります(というかこれでハマりました)。

どうもmdfファイルを直接消してしまうとユーザーインスタンス側になんらかのゴミが残るようです。
もしおかしな状態になってしまったら、次のような処理でユーザーインスタンスの状態をクリアしてください。

1)PCを再起動する
2)再起動後、すぐにSQL Server Expressのサービスを停止する
3)以下のフォルダを削除する
      C:\Users\ログインユーザー名\AppData\Local\Microsoft\Microsoft SQL Server Data\SQLEXPRESS

mvcConf @:Japan ビデオ公開

mvcConf @:Japan ~ ASP.NET MVC ブートキャンプ ~

mvcConf @:Japan の各セッションおよび会場で流れたビデオ等が公開されました。
なんと公開先はChannel9です。
いいのかなぁ。。。

私のセッションについては以下のページで見ることができます。
まぁ、セッションのビデオは画面の映像だけなのがちょっと安心できるとこ(w

http://channel9.msdn.com/Events/mvcConf/Japan/mvcConf-Japan/Session4

セッションは45分と短い時間だったため、実はBlogに書いた内容のほうが詳しくなっています。
それでもなんなので、ほんとに一部だけBlogに書いていない話もあったりしますが。

よろしかったら見てやってください。

#自分ははずかしいので見れない気がする。。。

mvcConf @:Japan ふりかえり 5: 同時実行制御

2011年6月21日 1件のコメント

スライド15

CodeFirstとASP.NET MVCとの組み合わせでは同時実行制御も簡単に実装することができます。
ふりかえり1で作成したサンプルプログラムを同時実行制御のエラーチェックができるように修正してみましょう。

まず、POCOのクラスに以下のようにTimestamp属性を付加した制御用の項目を追加します

—————————————————————————————————–

public class Member
{
    public int ID { get; set; }
    [Required(ErrorMessage="名前は必須入力です")]
    public string Name { get; set; }
    [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "メールが正しくありません")]
    public string Mail { get; set; }
    public int CategoryID { get; set; }
    public DateTime? BirthDay { get; set; }
    public int? Age
    {
        get
        {
            int? age = null;
            if (BirthDay != null)
            {
                age = DateTime.Now.Year – BirthDay.Value.Year;
                if (DateTime.Now < BirthDay.Value.AddDays(-1).AddYears(age.Value)) age -= 1;
            }
            return age;
        }
    }
    [Timestamp]
    public Byte[] Timestamp { get; set; }

    public virtual Category Category { get; set; }

}

—————————————————————————————————–

これにより、データベースにTimestamp項目が追加されます。
データの型がByte[]型であることに注意しておきましょう。

スキャッフォールドで生成されるコントローラー、ビューはTimestamp項目がないときと同じです。
そのままだと更新時にエラーとなるので、Editビュー(Edit.cshtml)にhiddenとしてTimestamp項目を追加します。

—————————————————————————————————–

@model MyDal.Member

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Member</legend>

        @Html.HiddenFor(model => model.ID)

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Mail)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Mail)
            @Html.ValidationMessageFor(model => model.Mail)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.CategoryID, "Category")
        </div>
        <div class="editor-field">
            @Html.DropDownList("CategoryID", String.Empty)
            @Html.ValidationMessageFor(model => model.CategoryID)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.BirthDay)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.BirthDay)
            @Html.ValidationMessageFor(model => model.BirthDay)
        </div>
        @Html.HiddenFor(Model => Model.Timestamp)
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

—————————————————————————————————–

同時実行制御のエラーが発生したときは例外としてDbUpdateConcurrencyExceptionがthrowされるので、このエラーをキャッチします。
実際のエラー時の対処はシステムの仕様によって異なります。
ここではとりあえずエラー時のメッセージを表示するようにだけEditアクションに手を入れます。

—————————————————————————————————–

[HttpPost]
public ActionResult Edit(Member member)
{
   try
    {
        if (ModelState.IsValid)
        {
            db.Entry(member).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    }
    catch (DbUpdateConcurrencyException e)
    {
        ModelState.AddModelError(string.Empty, e.Message);
    }
    ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name", member.CategoryID);
    return View(member);
}

—————————————————————————————————–

このようにサンプルプログラムを修正して実行します。
ブラウザを2つ起動して、片方でEdit画面を表示させた状態でもう1つのブラウザから同じデータを更新します。
その後、先にEdit画面を表示していたほうでデータを更新しようとすると以下のようなエラーが表示されます。

image

このサンプルで基本的な動作は理解していただけると思います。
より実際の動きに近い形の同時実行制御のサンプルがこちらで公開されています。
ぜひ参考にしてみてください。

mvcConf @:Japan ふりかえり 3: MVC 3 のスキャッフォールディング

2011年6月16日 1件のコメント

スライド10

ASP.NET MVC 3では、スキャッフォールドの形式が変わり、コントローラーの追加からビューの作成までを一度に行うことができるようになりました。

image_thumb1

オプションのテンプレートで上記のように設定すると、指定したコントローラーとIndex.cshtml、Create.cshtml、Details.cshtml、Edit.cshtml、Delete.cshtmlの5つのビューを作成してくれます。

データコンテキストクラスにはCodeFirstで作成するDbContextを継承したクラス以外に、Entity Frameworkのデータベースファースト、モデルファーストで仕様するObjectContextを継承したクラスが利用できます。
DbContextを利用している場合とObjectContextを利用している場合ではコントロールクラス内に生成されるプログラムが異なっており、それぞれにあわせて適切なメソッドを利用するようになっています。

このことからもわかるように、MVC 3 のスキャッフォールディングはデータの操作のサンプルとして有用です。
どのようなメソッドでデータ操作を行っているか確認してみましょう。

なお、テストを構築するためにリポジトリパターンでコントローラーを作成するサンプルがほしい、という場合はNuGetからインストールできるMvcScaffoldingを試してみてください。
リポジトリパターンを利用したコントローラーだけでなく、そのテストメソッドまでを生成することができます。
リポジトリパターンを利用しない場合でも、MvcScaffoldingで生成されるコントローラー、ビューは標準のスキャッフォールディングで作成されるプログラムと少し異なりますので、どこが違うかを見てみるのも勉強になると思います。

スライド11

DbContextでは単一のデータを取得するのにFindメソッドが使えます。
従来だとSingleメソッド等を使っていた部分なので、引数に m => m.ID == id といったラムダ式を記述する必要がありました。
少しですがタイプ数が改善されています。

複数のデータを取得する場合、特にナビゲーションプロパティに含まれる値を取得するにはIncludeメソッドを利用します。
ちなみに、ナビゲーションプロパティにvirtualの設定をしてレイジーローディングを有効にしている場合はIncludeを利用しなくてもプログラム自体は動作します。
ただしこの場合、必要になった時点でデータの取得が行われるため、複数回の問い合わせがデータベースに対して行われることになります。
Includeを利用することでデータベースへの問い合わせ(コストが高い)の回数を減らすことができます。

スライド12

データ更新時のEntryメソッドもDbContextで新しく追加されたメソッドです。
従来ですとAttachした後に更新状態を変更する、という2度の手間が必要だった作業を1行に簡潔に記述できるようになっています。

なお、CodeFirstではDbSetを用いることでデータの状態変更を追跡することができるようになっています。
これはWPFアプリケーション等クライアント側でデータの状態(ステート)を保持して操作する場合には大変有効な機能です。
ただ、Webアプリケーションは基本がステートレスとなり、リクエストを超えてデータの状態を保持することは逆に面倒になります。
上記のようにリクエストにより与えられたデータをAttachし、データ更新を行う、というのが一般的な手順となります。

スライド13

MVC 3のスキャッフォールディングで個人的に一番感銘を受けたのがDropDownListを利用するためのプログラムの生成の部分です。
サンプルプログラムで作成したような1対多の状態を作成していると、自動的にDropDownListによるマスター項目の選択ができるようになっています。
これを実現するのに、上記の2行のコードしか必要ではありません。
ものすごくシンプルです。

この書き方は参考になりますが、なかの仕組みはわかりにくいかと思います。
なぜこれで動くのか、というのを掘り下げてみると、いろいろと応用がきくのではないかと思います。

mvcConf @:Japan ふりかえり 2: CodeFirstでのデータベース定義の概要

2011年6月16日 1件のコメント

スライド5

CodeFirst ではクラスを定義すればそれがそのままテーブルの設計となります。
データベースにあまり強くないプログラマにとってこれはうれしいことのようですが、実際にはそうでもありません。
どのようにテーブル設計をするのがよいのかよく考えたうえでクラスの定義を行う必要があります。
ただ、そのクラス定義を必要があれば簡単に変更し、すぐにデータベースに反映していくことができる、という意味で、やはりCodeFirstはプログラマにとって大変魅力的なものとなります。

このクラス定義=テーブル設定においては、CoCの原則が適用されます。
たとえば、

  • ID、またはクラス名+IDという名前のプロパティは自動的に主キーとなる
  • ナビゲーションプロパティを定義すると、それにあわせてリレーションが設定される
  • リレーションの相手となるクラス名+IDという名前のプロパティは外部キーとなる
  • ナビゲーションプロパティに virtual を指定することでレイジーローディングが有効になる
  • 文字列型のプロパティはnull許容の設定になる
  • 値型のプロパティはnull否許容となる。nullable型(?をつける)とするとnull許容の設定になる
  • getメソッドしかもたないプロパティ項目はデータベース上に作成されず、計算で表示される項目となる

といった規約が利用できます。

これらの規約にたいし、属性を設定することでデータベース上の設定を変更することができます。
たとえば [Required] を付加することで文字列型のプロパティでもnull否許容とすることができます。
また、[MaxLength] により、データベース上の文字の最大値を設定することができます。

属性での設定以上に細かい定義をしたい場合はFluent APIと呼ばれるAPIを利用して設定を行うことになります。

スライド6

MemberクラスのNameプロパティに [Required] の設定を行うのと同じことをFluent APIで記述すると上記のようになります。
注意が必要なのは、Fluent APIを利用することでデータベース上の定義は変更することができますが、これはASP.NET MVCとしての動作にはそのままでは影響を与えない、という点です。
データベース上でName項目はnull否許容となるので、データの更新時に値が入っていなければエラーとなります。
ただし、[Required] という属性定義を利用した場合、あとで述べるASP.NET MVCのバリデーションの機能が働くため、エラーとなるまえに利用者にメッセージを表示して注意を促すことができます。

なお、Fluent APIを利用すると細かい定義を設定することが可能です。
特にすでに存在しているデータベースとPOCOクラスを関連づけたい、という場合はFluent APIを利用しないといけない場面があるでしょう。

スライド7

単なるクラス定義を実際にデータベース定義に結びつけるのがDbContextを継承したクラスでのDBSet<T>型のプロパティ定義です。
テーブル名はここで指定されたプロパティの名称になりますが、プロパティ名を単数形で指定してもテーブル名は複数形になるようです。
なお、テーブル名はPOCOクラスに[Table]メソッドを付加することで指定することが可能ですし、Fluent APIを用いて指定することも可能です。

スライド8

データベースを作成する場所としては、DbContextを継承したクラスの名称と同じ名称が設定されている接続文字列が探されます。
もしそのような接続文字列が存在しない場合、CodeFirstはSQL ServerのSQLExpressインスタンス(VisualStudioをインストールする際、デフォルトでSQL Server Expressのインスタンスとして作成される)の中にデータベースを作成します。
このとき作成されるデータベースの名前はnamespaceを含めたDbContextを継承したクラス名となります。

接続文字列がみつかった場合はその接続文字列に指定されている場所、名前でデータベースが作成されます。
このときSQLCe4を指定することもできます。

スライド9

デフォルトではCodeFirstによるデータベースの生成は一度しか行われず、定義の変更も行われません。
このため、POCOのクラス定義を変更すると、エラーが発生します。
Initializerの基底となるクラスを変更し、アプリケーションの実行時にInitializerを設定することでこの動作を変更することができます。

Initializerの基底となることができるクラスには以下のものがあります。

  • CreateDatabaseIfNotExists
  • DropCreateDatabaseIfModelChanges
  • DropCreateDatabaseAlways

それぞれの役割は名前のとおりです。

また、InitializerクラスのSeedメソッドをオーバーライドすることでデータベースの初期値を設定することができます。
データベースの作成時に常に同じデータをセットすることができるため、テスト環境の構築には有効だと思われます。
また、マスターデータ等初期状態で必須となるデータをデータベースに設定するのにも使えると思います。

mvcConf @:Japan ふりかえり 1: MVC3 + CodeFirst デモ

2011年6月12日 3件のコメント

mvcConf @:Japan で「MVCのM」という題でセッションを行いました。
十分に話せなかった点も多いので、何回かにわけてBlog上でセッションを再構成してみたいと思います。

最初にMVC3とEF 4.1 CodeFirstを使ってサンプルアプリケーションを構築します。
前提としてASP.NET MVC 3 Tools Updateが動作する環境を用意してください。
セッション中では説明しませんでしたが、私のデモでは無償で利用できるVisual Web Developer 2010を使用していました。
もちろん VisualStudio 2010でもまったく同じことができます。

1) 新規に ASP.NET MVC 3 Web アプリケーションを作成します。

2) ModelsフォルダにMyDal.csクラス(名前は別でもかまいません)を追加し、下記のコードを記述します。
*実際のセッションではあらかじめ作成してあったcsファイルを追加しました。

—————————————————————————————–

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;

namespace MyDal
{
    public class Member
    {
        public int ID { get; set; }
        [Required(ErrorMessage="名前は必須入力です")]
        public string Name { get; set; }
        [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "メールが正しくありません")]
        public string Mail { get; set; }
        public int CategoryID { get; set; }
        public DateTime? BirthDay { get; set; }
        public int? Age
        {
            get
            {
                int? age = null;
                if (BirthDay != null)
                {
                    age = DateTime.Now.Year – BirthDay.Value.Year;
                    if (DateTime.Now < BirthDay.Value.AddDays(-1).AddYears(age.Value)) age -= 1;
                }
                return age;
            }
        }

        public virtual Category Category { get; set; }
    }

    public class Category
    {
        public int CategoryID { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Member> Members { get; set; }
    }

    public class GroupContext : DbContext
    {
        public DbSet<Member> Members { get; set; }
        public DbSet<Category> Categories { get; set; }
    }

    public class GroupInitializer : DropCreateDatabaseIfModelChanges<GroupContext>
    {
        protected override void Seed(GroupContext context)
        {
            var categories = new List<Category>
            {
                new Category{ Name = "仕事"},
                new Category{ Name = "オンライン"},
                new Category{ Name = "オフライン"},
            };
            categories.ForEach(s => context.Categories.Add(s));
            context.SaveChanges();
        }
    }
}

—————————————————————————————–

3) web.config にDB接続文字列を追加します。

—————————————————————————————–

<add name="GroupContext" connectionString="Data Source=|DataDirectory|\Group.sdf" providerName="System.Data.SqlServerCe.4.0"/>

—————————————————————————————–

4) Global.asax.cs 内の Application_Start() メソッドに DB 初期化のコードを追加します。

—————————————————————————————-

System.Data.Entity.Database.SetInitializer<MyDal.GroupContext>(new MyDal.GroupInitializer());

—————————————————————————————-

5) ビルドを実行します。

6) Controllers フォルダを右クリックし、「追加」-「コントローラー」を選択します。

7) MemberController を以下の設定で追加(スキャッフォールディング)します。

image

8 ) MemberController.cs ファイル および Memberの各種ビューが追加されたことを確認します。

image

9) デバッグを開始し、「http://localhost:ポート番号/Member」 にアクセスします。

image

10) Create New リンクをクリックします。

image

11) Mail 項目に「test」とだけ入力し、Createボタンを押してエラー表示を確認します。

image

12) 項目に適当な値を入力し、Createボタンをクリックしてデータが登録されることを確認します。

image

image

13) ソリューションエクスプローラーで「すべてのファイルを表示」し、Group.sdfファイルが作成されていることを確認します。
このとき一度最新の情報に更新する必要があるかもしれません。

image

14) Group.sdfファイルを右クリックし、「プロジェクトに含める」を選択します。

15) データーベースエクスプローラーで CategoriesテーブルやMembersテーブルが作成されていることを確認します。

image

ふりかえりの1回目の内容としては、まずはここまで。
サンプルアプリケーションができたら、データの追加/更新/削除ができることを確認してください。