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 ふりかえり 6: Webサービス(OData)連携
.NET Frameworkを利用している場合、WCF Data Servicesを利用することでODataベースのWebサービスを簡単に提供することができます。
このWCF Data Services と連携するASP.NET MVC 3 のプログラムを作成してみます。
まずはWCF Data Servicesの実装、ということで空のWebアプリケーションプロジェクトを作成します。
WCF Data ServicesでCodeFirstを利用することは、いまのところ予定されている、という状態にとどまっています。
ここではデータベースファーストでWCF Data Servicesを構築することにします。
作成したWebアプリケーションにApp_Dataフォルダを追加し、そこに最初のサンプルで構築されたデータベース(Group.sdfファイル)を追加します。
この時点で一度ビルドを実行しておきます。
Entity Data ModelをGroupModel.edmxという名前で追加します。
ウィザードはデフォルトの状態のまますすめていきます。
テーブルをモデルに含めます。
モデルが作成されます。
この時点でまたビルドを実行します。
WCF Data Serviceのページを追加します。
データサービスのコードのひな形が作成されるので、以下の赤い部分を追加します。
—————————————————————————————–
using System.Data.Services;
using System.Data.Services.Common;
namespace WebApplication5
{
public class WcfDataService1 : DataService< GroupEntities >
{
// このメソッドは、サービス全体のポリシーを初期化するために、1 度だけ呼び出されます。
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
}
—————————————————————————————–
プログラムを実行すると、次のような画面が表示され、すでにWebサービスとしてAtomPabの形式でデータの提供が開始されることを確認できます。
これでWebサービス側のシステムが構築できたことになります。
このサービスは起動したまま、次の作業に入ります。
サービスが起動していないと以降の作業はうまく動作しませんので気をつけてください。
もうひとつVisual Web Developerを起動し、MVCのプロジェクトを作成します。
後で記述するサンプルコードと名前空間を合わせるため、「MyService」という名前でプロジェクトを作成してください。
ソリューションエクスプローラー上で参照設定を右クリックし、サービス参照の追加を選択します。
アドレス欄にWebサービスのURLをコピーし、名前空間を「GroupServiceReference」としてOKボタンをクリックします。
コントローラーとしてMemberController.cs を作成し、以下のコードを記述します。
—————————————————————————————–
using System;
using System.Linq;
using System.Web.Mvc;
using System.Data.Entity.Infrastructure;
using MyService.GroupServiceReference;
namespace MyServiceControllers
{
public class MemberController : Controller
{
private GroupEntities db = new GroupEntities(new Uri("WebサービスのURLをコピー"));
public ViewResult Index()
{
var member = db.Members.Expand("Categories");
return View(member.ToList());
}
public ViewResult Details(int id)
{
Members member = db.Members.Where(m => m.ID == id).First();
return View(member);
}
public ActionResult Create()
{
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name");
return View();
}
[HttpPost]
public ActionResult Create(Members member)
{
if (ModelState.IsValid)
{
db.AddToMembers(member);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name", member.CategoryID);
return View(member);
}
public ActionResult Edit(int id)
{
Members member = db.Members.Where(m => m.ID == id).First();
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name", member.CategoryID);
return View(member);
}
[HttpPost]
public ActionResult Edit(Members member)
{
if (ModelState.IsValid)
{
db.AttachTo("Members", member);
db.UpdateObject(member);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name", member.CategoryID);
return View(member);
}
public ActionResult Delete(int id)
{
Members member = db.Members.Where(m => m.ID == id).First();
return View(member);
}
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Members member = db.Members.Where(m => m.ID == id).First();
db.DeleteObject(member);
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
—————————————————————————————–
このプログラムはCodeFirstのサンプルでスキャッフォールドで生成したコードをもとにしています。
CodeFirstではDbContextを利用していましたが、WCF Data Servicesを参照したこのプログラムではDataServiceContextを利用することになります。
基底となるクラスが用意するメソッドが異なるため、おおきな流れはスキャッフォールドで生成したコードから変えていませんが、利用しているメソッドは異なっている点に注意してください。
次にViewを作成していきます。
まずViewフォルダにMemberフォルダを追加し、その中に下記のコードと追加していきます。
・Create.cshtml
—————————————————————————————–
@model MyService.GroupServiceReference.Members
@{
ViewBag.Title = "Create";
}
<h2>Create</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>
<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>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
—————————————————————————————–
・Delete.cshtml
—————————————————————————————–
@model MyService.GroupServiceReference.Members
@{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<fieldset>
<legend>Member</legend>
<div class="display-label">Name</div>
<div class="display-field">
@Html.DisplayFor(model => model.Name)
</div>
<div class="display-label">Mail</div>
<div class="display-field">
@Html.DisplayFor(model => model.Mail)
</div>
<div class="display-label">Category</div>
<div class="display-field">
@Html.DisplayFor(model => model.Categories.Name)
</div>
<div class="display-label">BirthDay</div>
<div class="display-field">
@Html.DisplayFor(model => model.BirthDay)
</div>
</fieldset>
@using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete" /> |
@Html.ActionLink("Back to List", "Index")
</p>
}
—————————————————————————————–
・Details.cshtml
—————————————————————————————–
@model MyService.GroupServiceReference.Members
@{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<fieldset>
<legend>Member</legend>
<div class="display-label">Name</div>
<div class="display-field">
@Html.DisplayFor(model => model.Name)
</div>
<div class="display-label">Mail</div>
<div class="display-field">
@Html.DisplayFor(model => model.Mail)
</div>
<div class="display-label">Category</div>
<div class="display-field">
@Html.DisplayFor(model => model.Category.Name)
</div>
<div class="display-label">BirthDay</div>
<div class="display-field">
@Html.DisplayFor(model => model.BirthDay)
</div>
<div class="display-label">Age</div>
<div class="display-field">
@Html.DisplayFor(model => model.Age)
</div>
</fieldset>
<p>
@Html.ActionLink("Edit", "Edit", new { id=Model.ID }) |
@Html.ActionLink("Back to List", "Index")
</p>
—————————————————————————————–
・Edit.cshtml
—————————————————————————————–
@model MyService.GroupServiceReference.Members
@{
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>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
—————————————————————————————–
・Index.cshtml
—————————————————————————————–
@model IEnumerable<MyService.GroupServiceReference.Members>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
Name
</th>
<th>
Mail
</th>
<th>
Category
</th>
<th>
BirthDay
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Mail)
</td>
<td>
@Html.DisplayFor(modelItem => item.Categories.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.BirthDay)
</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>
—————————————————————————————–
Viewのコードはスキャッフォールドで作成したコードからmodelの名前空間を変更し、また、計算項目として追加していたAgeがデータベース内にないことからその表示部分を削っただけの修正しかしていません。
これでとりあえず動くサンプルが完成しました。
データの追加/削除/変更が自由に行え、それがWebサービスをとおしてデータベース内に反映されることを確認してみましょう。
少しさわっていると、バリデーションが行われないことに気づくと思います。
最初のサンプルと同様のバリデーションを組み込むにはMetaDataを利用するのが簡単です。
Modelsフォルダに以下のコードを追加します。
・MetaClass.cs
—————————————————————————————–
using System.ComponentModel.DataAnnotations;
namespace MyService.GroupServiceReference
{
[MetadataType(typeof(MyMetaData))]
public partial class Members
{
}
public class MyMetaData
{
[Required(ErrorMessage="名前は必須入力です")]
public object Name { get; set; }
[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "メールが正しくありません")]
public object Mail { get; set; }
}
}
—————————————————————————————–
これでバリデーションが実行されるようになります。
MetaDataを利用する際に一番注意が必要なことは上記のプログラムでいうとpartialクラスとして定義しているMembersの名前空間を含めた定義になります。
この部分がバリデーションをあてようとしている元のクラスと正しく一致しないと設定が反映されません。
やっかいなことに元のクラスと一致していない場合でもコンパイルエラーにはならないため、なぜ設定が反映されないのか悩んで悩んで、結局このクラス定義にミスがあった、ということがよくあります。
○ふりかえりのまとめ
ASP.NET MVC では、.NET Frameworkが用意しているデータ連携の機能すべてを利用できます。
CodeFirstベースでデータベースを利用することもできますし、このサンプルのようにODataベースのWebサービスを簡単に利用することもできます。
今回は説明していませんが、XMLベースでのデータ配信を取り込んで利用するのも簡単です。
そして、多少利用できるメソッドの違いはありますが、全体的な流れはModel側のデータがなんであっても変わりません。
ASP.NET MVC 3ではスキャッフォールドで生成されるコードがかなり参考にできるコードになったので、ぜひ一度自分で試して、中身を納得できるまでよく解析してみると良いと思います。
なおセッション中では特に言及しませんでしたが、このBlogの内容はすべて無償版であるVisual Web Developerで動作させ、確認を行っています。
WebPIからVisual Web Developerをインストールすると必要な環境はすべて整いますので、ぜひインストールして試してみてください。
ASP.NET MVCはWebアプリケーションの開発において非常に魅力的な技術です。
CodeFirstの登場により、開発者にとってはその魅力がより増した、と感じています。
これらの技術に触れる最初の手がかりとして、このBlog記事が役に立つといいな、と思っています。
mvcConf @:Japan ふりかえり 5: 同時実行制御
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画面を表示していたほうでデータを更新しようとすると以下のようなエラーが表示されます。
このサンプルで基本的な動作は理解していただけると思います。
より実際の動きに近い形の同時実行制御のサンプルがこちらで公開されています。
ぜひ参考にしてみてください。
mvcConf @:Japan ふりかえり 4: バリデーション(入力値チェック)
ASP.NET MVCでは入力値のチェックに属性の設定によるバリデーションが利用できます。
Modelとして作成したクラスのプロパティに属性を設定するだけで以下のようなエラーチェックが可能です。
ここではNameプロパティに
[Required(ErrorMessage="名前は必須入力です")]
Mailプロパティに
[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "メールが正しくありません")]
という属性を設定しています。
スキャッフォールディングで作成したビューでは、標準で上記の属性によるバリデーションがJQuery.Validateが利用され、クライアント側でチェックが行われるようになっています。
ビュー内に以下の記述がありますが、この記述をはずすとクライアント側でのチェックが行われなくなり、一度サーバーにリクエストをなげて、そこでエラーチェックが行われるようになります。
—————————————————————————————————–
<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>
—————————————————————————————————–
属性によるバリデーションは基本的に1つの項目(プロパティ)に対してチェックを行うものです。
複数の項目の値によってチェックを行いたい、という場合には属性ではチェックできません。
複数の項目間の値チェックを行うためにModelクラスに IValidatableObject を実装する、という方法が使えます。
Memberクラスを次のように修正します。
—————————————————————————————————–
public class Member : IValidatableObject
{
public int ID { get; set; }
[Required]
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 IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Age >= 20 && Mail == null)
yield return new ValidationResult("20歳以上メールアドレス必須");
}
}
—————————————————————————————————–
IValidatableObjectをMemberクラスの基底クラスとして設定し、Validateメソッドを実装しています。
Validateメソッド内ではエラーチェックを行い、エラーがある場合にyieldでValidationResultを返しています。
これにより、年齢が20歳以上になる場合にMailプロパティに値が入力されていないと、以下のようにエラーが表示されます。
バリデーションの実装方法にはほかにもいくつかの方法があります。
たとえばコントロール内でModelStateクラスにたいしてAddModelErrorメソッドを利用して直接エラー状態を設定する、といったことも可能です。
バリデーションの使い分けに関しては適材適所という考え方もありますが、むしろシステム全体として利用方針を決めておくべきでしょう。
バリデーションだけでなく、ASP.NET MVCは自由度が高いために同じことを実現するのに複数の実装方法が選択できる、ということが多いです。
このような場合、システムとしての実装方法の利用方針をあらかじめ決定しておくことで、統一感がありメンテナンスのしやすいシステムが構築できると思います。
mvcConf @:Japan ふりかえり 3: MVC 3 のスキャッフォールディング
ASP.NET MVC 3では、スキャッフォールドの形式が変わり、コントローラーの追加からビューの作成までを一度に行うことができるようになりました。
オプションのテンプレートで上記のように設定すると、指定したコントローラーとIndex.cshtml、Create.cshtml、Details.cshtml、Edit.cshtml、Delete.cshtmlの5つのビューを作成してくれます。
データコンテキストクラスにはCodeFirstで作成するDbContextを継承したクラス以外に、Entity Frameworkのデータベースファースト、モデルファーストで仕様するObjectContextを継承したクラスが利用できます。
DbContextを利用している場合とObjectContextを利用している場合ではコントロールクラス内に生成されるプログラムが異なっており、それぞれにあわせて適切なメソッドを利用するようになっています。
このことからもわかるように、MVC 3 のスキャッフォールディングはデータの操作のサンプルとして有用です。
どのようなメソッドでデータ操作を行っているか確認してみましょう。
なお、テストを構築するためにリポジトリパターンでコントローラーを作成するサンプルがほしい、という場合はNuGetからインストールできるMvcScaffoldingを試してみてください。
リポジトリパターンを利用したコントローラーだけでなく、そのテストメソッドまでを生成することができます。
リポジトリパターンを利用しない場合でも、MvcScaffoldingで生成されるコントローラー、ビューは標準のスキャッフォールディングで作成されるプログラムと少し異なりますので、どこが違うかを見てみるのも勉強になると思います。
DbContextでは単一のデータを取得するのにFindメソッドが使えます。
従来だとSingleメソッド等を使っていた部分なので、引数に m => m.ID == id といったラムダ式を記述する必要がありました。
少しですがタイプ数が改善されています。
複数のデータを取得する場合、特にナビゲーションプロパティに含まれる値を取得するにはIncludeメソッドを利用します。
ちなみに、ナビゲーションプロパティにvirtualの設定をしてレイジーローディングを有効にしている場合はIncludeを利用しなくてもプログラム自体は動作します。
ただしこの場合、必要になった時点でデータの取得が行われるため、複数回の問い合わせがデータベースに対して行われることになります。
Includeを利用することでデータベースへの問い合わせ(コストが高い)の回数を減らすことができます。
データ更新時のEntryメソッドもDbContextで新しく追加されたメソッドです。
従来ですとAttachした後に更新状態を変更する、という2度の手間が必要だった作業を1行に簡潔に記述できるようになっています。
なお、CodeFirstではDbSetを用いることでデータの状態変更を追跡することができるようになっています。
これはWPFアプリケーション等クライアント側でデータの状態(ステート)を保持して操作する場合には大変有効な機能です。
ただ、Webアプリケーションは基本がステートレスとなり、リクエストを超えてデータの状態を保持することは逆に面倒になります。
上記のようにリクエストにより与えられたデータをAttachし、データ更新を行う、というのが一般的な手順となります。
MVC 3のスキャッフォールディングで個人的に一番感銘を受けたのがDropDownListを利用するためのプログラムの生成の部分です。
サンプルプログラムで作成したような1対多の状態を作成していると、自動的にDropDownListによるマスター項目の選択ができるようになっています。
これを実現するのに、上記の2行のコードしか必要ではありません。
ものすごくシンプルです。
この書き方は参考になりますが、なかの仕組みはわかりにくいかと思います。
なぜこれで動くのか、というのを掘り下げてみると、いろいろと応用がきくのではないかと思います。