先の記事 MVC4 EF Code First では、Entity Framework Code First の機能を利用して、MVC4 インターネットアプリケーションの SQL Server データベースに Parents と Children という親子関係を持つ 2 つのテーブルを作成しました。
そのアプリケーションに Create アクションメソッドとビューを追加して、Parents テーブルと Children テーブルに親と子のデータを同時に登録する方法について書きます。
クライアントから上のようなユーザー入力画面を使って送信されてくるデータ(上の例では "日本太郎"、"日本花子"、"日本一郎" という 3 つの文字列)を、Web サーバーで受け取って、Create アクションメソッドのパラメータにバインド(モデルバインディング)してやる必要があります。
モデルバインディングとデータアノテーション検証(クライアントサイドを含む)が正しく行われるためには、以下の 2 つの点を考慮する必要があります。
-
input 要素の name 属性はデータがコレクションの場合 "prefix[index].Property" というパターンにする。
-
クライアント側での検証に必要な属性が追加されるよう、ビューに EditorFor のような Html ヘルパーを使う。
詳しくは、先の記事「コレクションのデータアノテーション検証」に書きましたので、興味がありましたら読んでください。
上の 2 点を考慮に入れて作った Controller の Create アクションメソッドと View のコードを下にアップしましたので見てください。
プラウザから /ParentChild/Create を GET 要求すると一番上の画像のように表示されます。そこでデータを入力して[Create]ボタンをクリックすると、クライアント側で入力データが検証されたあと、 [HttpPost] 属性を付与した方の Create アクションメソッドに POST されます。
下の画像(Visual Stidio でのデバッグ画面)を見てください。POST されてきたデータは Create アクションメソッドの parent パラメータに正しくモデルバインディングされています。
その際、付与したアノテーション属性(この記事の例では Parent, Child クラスの Name プロパティに付与した Required と StringLength)によってサーバー側で入力データの検証が行われ、その結果が ModelStateDictionary(Controller.ModelState プロパティで取得できます)に格納されます。
検証結果が OK(ModelState.IsValid が true)であれば、送信されてきたデータは SQL Server の Parents, Children テーブルに登録されます。
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/Create
// 引数 number は登録する子のレコード数。
// 今回はとりあえずデフォルトで 2 としてみた。
public ActionResult Create(int number = 2)
{
Parent parent = new Parent();
for (int i = 0; i < number; i++)
{
Child child = new Child();
parent.Children.Add(child);
}
return View(parent);
}
//
// POST: /ParentChild/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Parent parent)
{
if (ModelState.IsValid)
{
db.Parents.Add(parent);
// 2016/9/6 追記:
// 以下のコードは不用でした。db.Parents.Add(parent) だけ
// で親子両方の EntityState が Added になり、SaveChanges
// メソッドで DB に親子とも INSERT されます。
//for (int i = 0; i < parent.Children.Count; i++)
//{
// db.Children.Add(parent.Children[i]);
//}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(parent);
}
//・・・中略・・・
}
}
今回は簡略化のため、とりあえず、親 : 子 = 1 : 2 で固定としました。 1 : n で n をユーザーが設定できるようにする方法も別途書く予定です。
SQL Server の Parents テーブルの Id 列と Children テーブルの Parent_Id 列には外部キー制約が設けられていますので、先に Parents に親レコードを INSERT してから Children に子レコードを INSERT する必要があります(つまり階層更新が必要)。また、Id 列は IDENTITY なので INSERT 操作が完了するまで値が分からないという問題もあります。そのあたりは、仕組みは不明ですが、Entity Framework がうまくやってくれるようで、上のコードで問題なく Create できます。
View
@model Mvc4App2.Models.Parent
@{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create</h2>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<fieldset>
<legend>Book</legend>
<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>
<hr />
@for (int i = 0; i < Model.Children.Count; i++)
{
<div class="editor-label">
@Html.LabelFor(model => model.Children[i].Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Children[i].Name)
@Html.ValidationMessageFor(model =>
model.Children[i].Name)
</div>
<hr />
}
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
EditorFor(model => model.Name) からは name="Name"、EditorFor(model => model.Children[i].Name) からは name="Children[i].Name"(i は連番)という name 属性が生成されます。
SQL Server の Parents, Children テーブルで、Id 列はいずれも IDENTITY となっています。ユーザー入力は不要なのでその入力用の EditorFor ヘルパーは設けていません。
コードの最後の方の @Scripts.Render("~/bundles/jqueryval") はクライアント側でのユーザー入力検証用の jQuery ライブラリを登録するためのものです��これがないとクライアント側での検証はかかりませんので注意してください。
以上、とりあえず Create するまでを書きました。後日、別途、Edit および Delete する方法も書く予定です。階層更新は Delete がちょっと面倒な気がします。