喜帳面の日記

50歳越えおやじのASP.NET MVC への挑戦日記です。

Visual Studio Express 2012 for Web でいってみる  9.ストアドプロシジャを使ってみる(1/6)

 

MVC4でEntity Framework経由でストアドプロシジャ(SQLServer)を動かしてみる。(1/)その前の準備です

昨今、「コードファースト」とかよく聞くようになりましたね。「データベース設計をしっかりやってから。。。」という開発手法は、もう時代遅れかもしれません。でも、既 存システムを部分的にMVCに置き換える場合など、複雑なデータアクセスやバッチ処理など、既存のストアドプロシジャを使わざるを得ないケースもあるか と思います。で、今回は「MVC4でストアドプロシジャ」にチャレンジします。

まだ最後まで行きつけていないのですが、忘れないようにメモを残しておきます。

初心者が行き当たりばったりにいろいろ動かしてみたときのメモなので記載内容には勝手な解釈や誤りもあるかと思います。そのあたりのご指摘よろしくお願いします。

参考にした情報は

SQL Server 2008 R2 サンプルで学ぶ ASP.NET MVC アプリケーション開発』

http://msdn.microsoft.com/ja-jp/data/ff723829.aspx

MVC2と若干古いのですがいろいろ参考になりました。

尚、このページのサンプルのZIPファイル内のサンプルデータベース「NorthwindJ」を今回はサンプルとして使用しています。

あと、ブログ しばやん雑記 ASP.NET MVC 3 開発入門シリーズ 今回は特に

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

http://shiba-yan.hatenablog.jp/entry/20110211/1297427966

私のところの環境は以下の通りです。

Windows7

SQL Server 2012 Express Edition (64-bit)

Visual Studio Express 2012 for Web

チャレンジの手順は以下の通りです。

 


№1.テスト用のデータベースを用意する。

上記ページのサンプルのZIPファイル内にある「NorthwindJ」のバックアップファイルをリストアしてdbを作成。但し、今回使用するのはテーブル[運送会社]のみです。

テーブルの構造は、以下の作成用スクリプトを参照ください。

CREATE TABLE [dbo].[運送会社](
[運送コード] [int] IDENTITY(1,1) NOT NULL,
[運送会社] [nvarchar](40) NOT NULL,
[電話番号] [nvarchar](24) NULL,
 CONSTRAINT [PK_運送会社] PRIMARY KEY NONCLUSTERED 
(
[運送コード] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_運送会社_001] ON [dbo].[運送会社]
(
[運送会社] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO 

上記のように、

・テーブル名、列名が日本語になっている。

・主キーは[運送コード]でIDENTITY指定(自動的に連番がインクリメント)されている。

・テーブル名と同じ列名が存在している。([運送会社]) 

・列[運送会社]は意味的には、運送会社名です。

 また「省略不可」かつ「UNIQUE」(重複不可)となっています。

 ※列[運送会社]の制約は今回のテストの為に私が追加したものです。

 


2.テスト用のプロジェクトを作成する。

テスト用のプロジェクトを作成

・テンプレート:「C#ASP.NET MVC 4 Webアプリケーション」-「インターネットアプリケーション」を選択。

 №3.「edmx」ファイルを作成し、そして一気にCRUDページを作成する。

ストアドプロシジャを用意する前にちょっと寄り道になるけど、Entity Data Model ツールを使い、 テーブル[運送会社]だけでedmxファイルを用意して、CRUDを作ってみます。

 1.ソリューションエクスプローラー[Models]フォルダを右クリック。[追加]-[新しい項目]を選択。

f:id:SannomiyaNotes:20121127110314p:plain

 

 2.edmxファイルの指定

テンプレートは[Web]-[データ]をクリック、[ADO.NET Entity Data Model]を選択しファイル名を指定して、[追加]押下。

f:id:SannomiyaNotes:20121127111531p:plain

 

 3.Entity Data Model ウイザードの開始

モデルのコンテンツの選択は、「データベースから生成」を選択

f:id:SannomiyaNotes:20121127111912p:plain

 

 4.Entity Data Model ウイザード データ接続の選択と新しい接続の作成

f:id:SannomiyaNotes:20121127112838p:plain

 

 5.Entity Data Model ウイザード データベースオブジェクトと設定の選択

テーブルのツリーより[運送会社]を指定して[完了]を押下。

f:id:SannomiyaNotes:20121127113452p:plain

セキュリティ警告が出たら、一応メッセージを読んで納得の上[OK]押下。

Entity Data Model ウイザードが完了。

※「生成されたオブジェクトの名前を複数化する」を指定しています。今回の事例ではテーブル名が日本語なので意味をもたないのですが、ここにチェックを入れるとコンテキスト名が自動的に複数化されます。例えば、テーブル名[Errorlog]ならコンテキストでは

public DbSet<Errorlog> Errorlogs { get; set; }

と生成されます。

単に s が付くわけではなく、ちゃんと英語っぽく、テーブル名[Person]は[People]になります。英語力が必要ですね。

 

6.NorthwindJ.edmxが生成されます。

中身をみてみましょう。

f:id:SannomiyaNotes:20121127114810p:plain

いろんなファイルが追加されています。

例えば、NorthwindJ.Context.cs

f:id:SannomiyaNotes:20121127115524p:plain

 運送会社.cs

f:id:SannomiyaNotes:20121127115921p:plain

また[NorthwindJ.edmx]をクリックし選択した状態で、下にある[モデルブラウザー]タグをクリックすると以下のような画面となります。

f:id:SannomiyaNotes:20121127120654p:plain

Modelのエンティティ型、EntityContainerのエンティティセット、そしてStoreのテーブル/ビューに運送会社が登場しています。

また、左側のダイアグラムの[運送会社](テーブル名)を右クリックしドロップダウンリストから[テーブルマッピング]を選択すると以下のようなマッピングの詳細ダイアログが表示されます。

f:id:SannomiyaNotes:20121127121455p:plain

列[運送会社]は[運送会社1]としてマッピングされています。テーブル名と同じ列名が存在する場合、こんな風に変名されるようです。このダイアログを使って列[運送会社]を[運送会社名]とかにマップしてもいいですね。今回は[運送会社1]のままにしておきます。

これでModelは出来上がりです。

 

7.重要 ここでファイルを保存しビルドしておく

 ここでビルドしておかないと以降の処理で変更内容が反映できません。

 例えばモデルクラスのドロップダウンリストに運送会社が登場しないなど。

 

8.スキャフォールディングを使ってCRUDページを作成する。

[運送会社]のModelが出来上がりました。次はコントローラーとビューを作成しますが、スキャフォールディング機能を使って自動的に作成してみます。

ソリューションエクスプローラーで、[Controllers]フォルダーを右クリックし、[追加]->[コントローラー]を選択します。

f:id:SannomiyaNotes:20121127132923p:plain

コントローラーの追加ダイアログ

f:id:SannomiyaNotes:20121127154225p:plain

指定内容は

コントローラー名:運送会社Controller

スキャフォールディングのオプション

テンプレート:Entity Frameworkを使用した。。。

モデルクラス:運送会社 (Mvc4ApplicationD.Models)

データコンテキストクラス:NorthwindJEntities (Mvc4ApplicationD.Models)

※Mvc4ApplicationDの部分はプロジェクト名です

この処理で、コントローラーとビューが追加されます。

コントローラー:[Controllers]フォルダに[運送会社Controller.cs]

ビュー:[Views]フォルダに[運送会社]が作成され、[Create.cshtml],[Delete.cshtml][Details.cshtml],[Edit.cshtml],[Index.cshtml]の5ファイルが作成されます。

では、ちょっと動かしてみます。

 

9.メニューを変更する。

[Views]-[Shared]フォルダの[_Layout.cshtml]を開いてメニュー部分を変更します。

<ul id="menu">
      <li>@Html.ActionLink("ホーム", "Index", "Home")</li>
      <li>@Html.ActionLink("バージョン情報", "About", "Home")</li>
      <li>@Html.ActionLink("連絡先", "Contact", "Home")</li>
</ul>
これを
<ul id="menu">
      <li>@Html.ActionLink("ホーム", "Index", "Home")</li>
      <li>@Html.ActionLink("バージョン情報", "About", "Home")</li>
      <li>@Html.ActionLink("連絡先", "Contact", "Home")</li>
      <li>@Html.ActionLink("運送会社", "Index", "運送会社")</li>
</ul>

 と最終行を追加します。

 これでページのヘッダー部に「運送会社」が追加されます。

f:id:SannomiyaNotes:20121127161914p:plain

 

10.メニューから起動する。

「運送会社」をクリックすると以下のようなリスト形式のページが表示されます。

また、「Create」をクリックすると新レコードの登録ページが表示されます。

f:id:SannomiyaNotes:20121127181850p:plain

 尚、各レコードの[Edit],[Details][Delete]が動作するようにするには、Index.cshtmlの以下の部分(コメントアウトされている)を変更する必要があります。

<td>
    @Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
    @Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
    @Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
これを
<td>
   @Html.ActionLink("Edit", "Edit", new { id=item.運送コード  }) |
   @Html.ActionLink("Details", "Details", new { id=item.運送コード}) |
   @Html.ActionLink("Delete", "Delete", new {  id=item.運送コード })
</td>

とキー項目を設定します。

さて、これでテーブル[運送会社]のCRUDページが一応出来上がりです。

ただ何点か改良しておきたい点があります。例えば

・運送コードはdb側で自動採番するのでCreateページでの入力は意味がない。

 --> Createページから削除したい。

・列[運送会社]は意味的には運送会社名だがラベルは[運送会社1]と表示されてる。

 --> ラベルの表示を[会社名]としたい。

・列[運送会社]は省略不可にしたい

 --> 省略不可のチェックをしたい。

などなど。

 

№4CRUDページをカスタマイズする。

上記のような要望を実現するため、

Entity Data Model ウイザードで生成された class [運送会社] から

 1.ビュー用のModelを新たに追加する

[Models]フォルダを右クリック、[追加]->[新しい項目]を選択。

テンプレートは[コード]、[クラス]を指定し、名前欄には例えば[運送会社ViewModel.cs]を入力して[追加]をクリック。

空の[運送会社ViewModel]が出来上がります。基本的なプロパティは[運送会社.cs](NorthwindJ.edmx のツリー配下にあります)からコピー&ペーストで持ってきて以下のような記述にしてみます。

public class運送会社ViewModel
{
    public int 運送コード { get; set; }
    public string 運送会社1 { get; set; }
    public string 電話番号 { get; set; }
}

 これに要件に合わせた[属性]を追加してみます。

public class 運送会社ViewModel
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    [DisplayName("コード")]
    public int 運送コード { get; set; }

    [Required]
    [DisplayName("会社名")]
    [StringLength(20, ErrorMessage = "{0} の長さは {1} 文字以内にしてください。")]
    public string 運送会社1 { get; set; }

    [DisplayName("電話番号")]
    [RegularExpression(@"(0\d{1,4}-|\(0\d{1,4}\) ?)?\d{1,4}-\d{4}", ErrorMessage = " 「-」で区切って入力してください。")]
    public string 電話番号 { get; set; }
}

 [属性]について説明できるほどのノウハウがありません。すみませんが他の情報を参照ください。ここでは、よく使いそうな、ものを指定してみました。

[DisplayName](System.ComponentModel)◦プロパティのラベルを指定

[Required](System.ComponentModel.DataAnnotations)。検証属性で入力必須の指定。

[StringLength](System.ComponentModel.DataAnnotations)。検証属性で文字の最大長指定。[StringLength(10,MInimumLength=6)]と書くと6~10文字のチェックがかけられます。

[RegularExpression](System.ComponentModel.DataAnnotations)。検証属性で入力された値が指定の正規表現式と一致しているかのチェックがかけられます。上記の例では電話番号にこの検証をつけてますが、あくまで説明用の「例」としてみてくださいね。

そして、

[Key](System.ComponentModel.DataAnnotations)
 [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
(System.ComponentModel.DataAnnotations.Schema)

この2つの[属性]は正直よくわかりません。列がIDENTITY指定されてるキーの場合これ付けておくと、スキャフォールディング時に生成対象から除外してくれるのでおまじないのようにつけています。

さて上記の属性ですがいろんな名前空間にまたがって存在してるんで以下の指定が必要になります。

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

上記3行を追加しておきます。

 

 2.ビューを再度作り直してみる。[Index]

№3- 8.で一旦作成した運送会社のCRUDですが、上記の新しいModelの[属性]を反映

させる為に、ビューを再作成します。再作成についてもスキャフォールディングが使えます。

まず、ビュー[Index]の再作成です。

1.既存のビューを削除。[Views]-[運送会社]-[Index.cshtml]を削除します。

2.[運送会社Controller.cs]を開いて、[Index]メソッド内で右クリックします。

3.リストから[ビューの追加]を選択

f:id:SannomiyaNotes:20121128175615p:plain

 4.ビューの追加ダイアログで

ビュー名:Index

ビューエンジン:Razor

厳密に型指定されたビューを作成する:チェック

モデルクラス名:運送会社ViewModel (Mvc4ApplicationD.Models)を選択

スキャフォールディングビューテンプレート:[List]を選択

レイアウトまたはマスターページを使用する:チェック

f:id:SannomiyaNotes:20121128180636p:plain

[追加]をクリックすると新しい[Index.cshtml]が作成されます。

5.[運送会社Controller.cs]を開いて、[Index]メソッドを変更します。

■変更前■
public ActionResult Index()
{
    return View(db.運送会社.ToList());
}
■変更後■  
public ActionResult Index()
{
    IList<運送会社ViewModel> model = new List<運送会社ViewModel>();
    foreach (運送会社 運送会社 in db.運送会社.ToList())
    {
        運送会社ViewModel item = new 運送会社ViewModel();
        item.運送コード = 運送会社.運送コード;
        item.運送会社1 = 運送会社.運送会社1;
        item.電話番号 = 運送会社.電話番号;
        model.Add(item);
    }
    var rmodel = model.OrderBy(t => t.運送コード); //明示的にソート
    return View(rmodel);
}

db.運送会社.ToList()で取得したレコードを[運送会社ViewModel]型のListにセットしてビューに渡しています。

 

 3.ビューを再度作り直してみる。[Create]

同様に、ビュー[Create]の再作成です。

1.既存のビューを削除。[Views]-[運送会社]-[Create.cshtml]を削除します。

2.[運送会社Controller.cs]を開いて、[Index]メソッド内で右クリックします。

3.リストから[ビューの追加]を選択

4.ビューの追加ダイアログで

ビュー名:Create

ビューエンジン:Razor

厳密に型指定されたビューを作成する:チェック

モデルクラス名:運送会社ViewModel (Mvc4ApplicationD.Models)を選択

スキャフォールディングビューテンプレート:[Create]を選択

レイアウトまたはマスターページを使用する:チェック

[追加]をクリックすると新しい[Create.cshtml]が作成されます。

5.[運送会社Controller.cs]を開いて、[Create]メソッドを変更します。

[Create]メソッドは2つありますが、[HttpPost]属性のついている、データの更新処理の方です。リクエストの方は今回は編集不要です。

[HttpPost]
public ActionResult Create(運送会社ViewModel model)
{
    if (ModelState.IsValid)
    {
        運送会社 運送会社 = new 運送会社();
        運送会社.運送コード = model.運送コード;
        運送会社.運送会社1 = model.運送会社1;
        運送会社.電話番号 = model.電話番号;
        db.運送会社.Add(運送会社);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(model);
}

 4.ビューを再度作り直してみる。[その他]

他の[Edit],[Details],[Delete]も前のやつを削除して、コントローラーの各メソッドより右クリックでスキャフォールディングを呼び出して作成します。

コントローラーも同様に修正が必要です。ちょっと長くなりますが[Edit],[Delete]部分を掲示します。

public ActionResult Edit(int id = 0)
{
    運送会社 運送会社 = db.運送会社.Find(id);
    if (運送会社 == null)
    {
        return HttpNotFound();
    }
    
    運送会社ViewModel model = new 運送会社ViewModel();
    model.運送コード = 運送会社.運送コード;
    model.運送会社1 = 運送会社.運送会社1;
    model.電話番号 = 運送会社.電話番号;
    return View(model);
}
//
// POST: /運送会社/Edit/5
[HttpPost]
public ActionResult Edit(運送会社ViewModel model)
{
    if (ModelState.IsValid)
    {
        運送会社 運送会社 = db.運送会社.Find(model.運送コード);
        運送会社.運送会社1 = model.運送会社1;
        運送会社.電話番号 = model.電話番号;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(model);
}
//
// GET: /運送会社/Delete/5
public ActionResult Delete(int id = 0)
{
    運送会社 運送会社 = db.運送会社.Find(id);
    if (運送会社 == null)
    {
        return HttpNotFound();
    }
    運送会社ViewModel model = new 運送会社ViewModel();
    model.運送コード = 運送会社.運送コード;
    model.運送会社1 = 運送会社.運送会社1;
    model.電話番号 = 運送会社.電話番号;
    return View(model);
}
//
// POST: /運送会社/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
    運送会社 運送会社 = db.運送会社.Find(id);
    db.運送会社.Remove(運送会社);
    db.SaveChanges();
    return RedirectToAction("Index");
}

いづれのパターンも、ビュー用のModelとEntity Framework用のclass間で相互に転送行っています。ちょっと手間ですね。でも、ビュー用のModelとデーターベースのテーブルって一致していないことが多いので、こんなパターンって多いんじゃないでしょうか。あと、今回のサンプルでは、コントローラー内にいろんな処理を書き込んでしまいましたが、実際は別のクラスファイルに分けることになると思います。

 

 №5.動かしてみる。

 

f:id:SannomiyaNotes:20121129142312p:plain

 

・各ページから[運送コード]が消えた。

・列[運送会社]は[会社名]と表示されるようになった。

・[会社名]は省略不可、かつ20文字以内のチェックがクライアントサイドで行われるようになり、エラー状態の場合、リアルタイムにエラー表示されます。

以上、ビュー用のモデルを作成しコントローラーを手直しして、スキャフォールディングでビューを自動作成することによってかなり使える感じになってきました。

今回は、既存のテーブルに対応するCRUDを作るケースでした。コードファーストの場合はまた違った開発手法になるんでしょう。

コードファーストでって場合に非常に参考になるのが以下のブログページです。

『しばやん雑記 ASP.NET MVC 3 開発入門 (3) - モデルをコードファーストで作成』

http://shiba-yan.hatenablog.jp/entry/20110211/1297427966

コードファーストの件以外にもMVCでの開発全般にわたって痒いところに手が届く情報が掲載されてます。開発入門 (1)~(24)までまだご覧になっていない方ぜひどうぞ。

 

さて、前置きが長くなってしまいました。

ストアドプロシジャを動かす件ですが、ごめんなさい、次回にします。

今回作成したプロジェクトを使ってデータアクセス部分をストアドプロシジャに置き換えていきます。

では。