チュートリアル: ASP.NET MVC アプリで EF を使用して関連データを更新する
前のチュートリアルでは、関連データを表示しました。 このチュートリアルでは、関連データを更新します。 ほとんどのリレーションシップでは、外部キー フィールドまたはナビゲーション プロパティを更新することでこれを行うことができます。 多対多リレーションシップの場合、Entity Framework は結合テーブルを直接公開しないため、適切なナビゲーション プロパティとの間でエンティティを追加および削除します。
以下の図は、使用するページの一部を示しています。
このチュートリアルでは、次の作業を行いました。
- コース ページをカスタマイズする
- 講師ページにオフィスを追加する
- コースを講師ページに追加する
- DeleteConfirmed の更新
- オフィスの場所とコースを Create ページに追加する
前提条件
コース ページをカスタマイズする
新しいコース エンティティが作成されると、既存の部門とのリレーションシップが必要になります。 これを容易にするため、スキャフォールディング コードには、コントローラーのメソッドと、部門を選択するためのドロップダウン リストを含む Create ビューと Edit ビューが含まれます。 ドロップダウン リストは、Course.DepartmentID
外部キー プロパティを設定します。これは、Department
ナビゲーション プロパティを適切な Department
エンティティとともに読み込むためにすべての Entity Framework で必要です。 このスキャフォールディング コードを使用しますが、エラー処理を追加し、ドロップダウン リストを並べ替えるために少し変更します。
CourseController.cs で、4 つの Create
メソッドと Edit
メソッドを削除し、次のコードに置き換えます。
public ActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID")]Course course)
{
try
{
if (ModelState.IsValid)
{
db.Courses.Add(course);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Course course = db.Courses.Find(id);
if (course == null)
{
return HttpNotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var courseToUpdate = db.Courses.Find(id);
if (TryUpdateModel(courseToUpdate, "",
new string[] { "Title", "Credits", "DepartmentID" }))
{
try
{
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}
private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
var departmentsQuery = from d in db.Departments
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
}
ファイルの先頭に次 using
のステートメントを追加します。
using System.Data.Entity.Infrastructure;
メソッドは PopulateDepartmentsDropDownList
、名前で並べ替えられたすべての部門のリストを SelectList
取得し、ドロップダウン リストのコレクションを作成し、コレクションをプロパティのビューに ViewBag
渡します。 このメソッドは、ドロップダウン リストがレンダリングされるときに選択される項目を指定するためのコード呼び出しを許可する、省略可能な selectedDepartment
パラメーターを受け取ります。 ビューは DropDownList ヘルパーに名前DepartmentID
を渡し、ヘルパーは という名前DepartmentID
の オブジェクトSelectList
をViewBag
検索することを認識します。
このメソッドは HttpGet
Create
、選択した項目を設定せずに メソッドを呼び出 PopulateDepartmentsDropDownList
します。これは、新しいコースでは部門がまだ確立されていないためです。
public ActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
メソッドは HttpGet
Edit
、編集中のコースに既に割り当てられている部門の ID に基づいて、選択したアイテムを設定します。
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Course course = db.Courses.Find(id);
if (course == null)
{
return HttpNotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
と Edit
の両方Create
のメソッドにはHttpPost
、エラーの後にページを再表示するときに選択した項目を設定するコードも含まれます。
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
このコードを使用すると、ページが再表示されてエラー メッセージが表示され、どの部門が選択された場合でも選択された状態が維持されます。
Course ビューは既に部署フィールドのドロップダウン リストでスキャフォールディングされていますが、このフィールドの DepartmentID キャプションは必要ないので、次の強調表示された変更を Views\Course\Create.cshtml ファイルに加えて、キャプションを変更します。
@model ContosoUniversity.Models.Course
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Course</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.CourseID)
@Html.ValidationMessageFor(model => model.CourseID)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Credits, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Credits)
@Html.ValidationMessageFor(model => model.Credits)
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="DepartmentID">Department</label>
<div class="col-md-10">
@Html.DropDownList("DepartmentID", String.Empty)
@Html.ValidationMessageFor(model => model.DepartmentID)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Views\Course\Edit.cshtml で同じ変更を行います。
通常、スキャフォールディングは主キーをスキャフォールディングしません。これは、キー値がデータベースによって生成され、変更することはできないため、ユーザーに表示される意味のある値ではありません。 Course エンティティの場合、スキャフォールディングにはフィールドの CourseID
テキスト ボックスが含まれています。これは、 DatabaseGeneratedOption.None
属性がユーザーが主キー値を入力できることを意味することを理解しているためです。 ただし、数値が意味を持つので、他のビューに表示する必要があるため、手動で追加する必要があることを理解していません。
Views\Course\Edit.cshtml で、[タイトル] フィールドの前にコース番号フィールドを追加します。 主キーであるため、表示されますが、変更することはできません。
<div class="form-group">
@Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DisplayFor(model => model.CourseID)
</div>
</div>
[編集] ビューには、コース番号の非表示フィールド (Html.HiddenFor
ヘルパー) が既に存在します。 Html.LabelFor ヘルパーを追加しても、ユーザーが [編集] ページで [保存] をクリックしても、コース番号が投稿されたデータに含まれないため、非表示フィールドの必要性はなくなります。
Views\Course\Delete.cshtml と Views\Course\Details.cshtml で、部門名 キャプションを "Name" から "Department" に変更し、[タイトル] フィールドの前にコース番号フィールドを追加します。
<dt>
Department
</dt>
<dd>
@Html.DisplayFor(model => model.Department.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.CourseID)
</dt>
<dd>
@Html.DisplayFor(model => model.CourseID)
</dd>
[作成] ページを実行し ([コース インデックス] ページを表示し、[新規作成] をクリックします)、新しいコースのデータを入力します。
値 | 設定 |
---|---|
Number | 「1000」と入力します。 |
タイトル | 「 代数」と入力します。 |
謝辞 | 「4」と入力します。 |
部署 | [ 数学] を選択します。 |
Create をクリックしてください。 [コースインデックス] ページが表示され、新しいコースがリストに追加されます。 Index ページのリストの部門名は、ナビゲーション プロパティから取得され、リレーションシップが正常に確立されていることを示しています。
[編集] ページを実行します ([コース インデックス] ページを表示し、コースの [編集] をクリックします)。
ページ上のデータを変更し、 [Save](保存) をクリックします。 [コース インデックス] ページに、更新されたコース データが表示されます。
講師ページにオフィスを追加する
インストラクター レコードを編集するときに、インストラクターのオフィスの割り当ての更新が必要な場合があります。 エンティティには Instructor
、エンティティとの OfficeAssignment
1 対 0 または 1 のリレーションシップがあります。つまり、次の状況に対処する必要があります。
- ユーザーがオフィスの割り当てをクリアし、最初に値を持っていた場合は、エンティティを削除して削除する
OfficeAssignment
必要があります。 - ユーザーがオフィス割り当て値を入力し、最初は空だった場合は、新
OfficeAssignment
しいエンティティを作成する必要があります。 - ユーザーがオフィスの割り当ての値を変更する場合は、既存
OfficeAssignment
のエンティティの値を変更する必要があります。
InstructorController.cs を開き、 メソッドをHttpGet
Edit
見てください。
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Instructor instructor = db.Instructors.Find(id);
if (instructor == null)
{
return HttpNotFound();
}
ViewBag.ID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.ID);
return View(instructor);
}
ここでのスキャフォールディングされたコードは、必要な内容ではありません。 ドロップダウン リストのデータを設定していますが、必要なのはテキスト ボックスです。 このメソッドを次のコードに置き換えます。
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
if (instructor == null)
{
return HttpNotFound();
}
return View(instructor);
}
このコードは ステートメントを ViewBag
削除し、関連付けられた OfficeAssignment
エンティティの一括読み込みを追加します。 メソッドを使用して Find
一括読み込みを実行することはできないため、 Where
代わりに メソッドと Single
メソッドを使用して講師を選択します。
メソッドを HttpPost
Edit
次のコードに置き換えます。 Office 割り当ての更新プログラムを処理する:
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
if (TryUpdateModel(instructorToUpdate, "",
new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
{
try
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
return View(instructorToUpdate);
}
への RetryLimitExceededException
参照には ステートメントが必要です using
。追加するには、 の上にマウス ポインターを置 RetryLimitExceededException
きます。 次のメッセージが表示されます:
[潜在的な修正プログラムを表示する] を選択し、System.Data.Entity.Infrastructure を使用します
このコードは次のことを行います。
シグネチャがメソッドと同じ
HttpGet
になったため、メソッド名を にEditPost
変更します (属性はActionName
、/Edit/ URL が引き続き使用されることを指定します)。OfficeAssignment
ナビゲーション プロパティの一括読み込みを使用して、現在のInstructor
エンティティをデータベースから取得します。 これは、 メソッドで行った操作とHttpGet
Edit
同じです。モデル バインダーからの値を使用して、取得した
Instructor
エンティティを更新します。 使用される TryUpdateModel オーバーロードを使用すると、含めるプロパティを一覧表示できます。 これにより、 2 番目のチュートリアルで説明されているように、過剰な投稿が防止されます。if (TryUpdateModel(instructorToUpdate, "", new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
オフィスの場所が空白の場合は、
OfficeAssignment
テーブル内の関連する行が削除されるように、Instructor.OfficeAssignment
プロパティを null に設定します。if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location)) { instructorToUpdate.OfficeAssignment = null; }
データベースへの変更を保存します。
Views\Instructor\Edit.cshtml で、Hire Date フィールドの要素のdiv
後に、オフィスの場所を編集するための新しいフィールドを追加します。
<div class="form-group">
@Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.OfficeAssignment.Location)
@Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
</div>
</div>
ページを実行します ([ 講師 ] タブを選択し、講師の [編集 ] をクリックします)。 [Office Location](オフィスの場所) を変更し、 [Save](保存) をクリックします。
コースを講師ページに追加する
インストラクターは、任意の数のコースを担当する場合があります。 次に、チェック ボックスのグループを使用してコースの割り当てを変更する機能を追加して、[講師の編集] ページを強化します。
エンティティと Instructor
エンティティの間のCourse
リレーションシップは多対多であるため、結合テーブルにある外部キー プロパティに直接アクセスすることはできません。 代わりに、ナビゲーション プロパティとの間でエンティティを追加および Instructor.Courses
削除します。
インストラクターに割り当てられるコースを変更できるようにする UI は、チェック ボックスのグループです。 データベース内のすべてのコースのチェック ボックスが表示され、インストラクターに現在割り当てられているコースが選択されます。 ユーザーは、チェック ボックスをオンまたはオフにしてコースの割り当てを変更できます。 コースの数がはるかに多い場合は、ビューにデータを表示する別の方法を使用する必要がありますが、リレーションシップを作成または削除するには、ナビゲーション プロパティを操作する同じ方法を使用します。
チェック ボックスのリストのためにデータをビューに提供するには、ビュー モデル クラスを使用します。 ViewModels フォルダーに AssignedCourseData.cs を作成し、既存のコードを次のコードに置き換えます。
namespace ContosoUniversity.ViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
InstructorController.cs で、 メソッドをHttpGet
Edit
次のコードに置き換えます。 変更が強調表示されます。
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.ID == id)
.Single();
if (instructor == null)
{
return HttpNotFound();
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
private void PopulateAssignedCourseData(Instructor instructor)
{
var allCourses = db.Courses;
var instructorCourses = new HashSet<int>(instructor.Courses.Select(c => c.CourseID));
var viewModel = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
viewModel.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
ViewBag.Courses = viewModel;
}
このコードは、Courses
ナビゲーション プロパティに一括読み込みを追加し、新しい PopulateAssignedCourseData
メソッドを呼び出して、AssignedCourseData
ビュー モデル クラスを使用してチェック ボックス配列に情報を提供します。
PopulateAssignedCourseData
メソッド内のコードは、ビュー モデル クラスを使用してコースのリストを読み込むため、すべての Course
エンティティを読み取ります。 各コースに対し、コードはそのコースがインストラクターの Courses
ナビゲーション プロパティ内に存在しているかどうかをチェックします。 コースがインストラクターに割り当てられているかどうかを確認するときに効率的なルックアップを作成するために、インストラクターに割り当てられたコースは HashSet コレクションに格納されます。 プロパティは Assigned
、講師が割り当てられているコースに対して に true
設定されます。 ビューは、このプロパティを使用して、どのチェック ボックスを選択済みとして表示する必要があるかを判断します。 最後に、リストは プロパティのビューに ViewBag
渡されます。
次に、ユーザーが [Save](保存) をクリックしたときに実行されるコードを追加します。 メソッドを EditPost
、エンティティのナビゲーション プロパティを更新 Courses
する新しいメソッドを呼び出す次のコードに Instructor
置き換えます。 変更が強調表示されます。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int? id, string[] selectedCourses)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.ID == id)
.Single();
if (TryUpdateModel(instructorToUpdate, "",
new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
{
try
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.Courses.Select(c => c.CourseID));
foreach (var course in db.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Remove(course);
}
}
}
}
メソッドシグネチャは メソッドと HttpGet
Edit
異なるため、メソッド名は から EditPost
に Edit
変更されます。
ビューにはエンティティの Course
コレクションがないため、モデル バインダーはナビゲーション プロパティを自動的に Courses
更新できません。 モデル バインダーを使用してナビゲーション プロパティを Courses
更新する代わりに、新しい UpdateInstructorCourses
メソッドで行います。 そのため、モデル バインドから Courses
プロパティを除外する必要があります。 明示的なリスト オーバーロードCourses
を使用しており、インクルード リストにないため、TryUpdateModel を呼び出すコードを変更する必要はありません。
チェック ボックスが選択されていない場合、 のUpdateInstructorCourses
コードは、空のコレクションを使用してCourses
ナビゲーション プロパティを初期化します。
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
その後コードは、データベース内のすべてのコースをループ処理し、各コースを現在インストラクターに割り当てられているコースとビューで選択されているコースを比較してチェックします。 検索を効率化するため、最後の 2 つのコレクションが HashSet
オブジェクトに格納されます。
コースのチェック ボックスが選択されたが、そのコースが Instructor.Courses
ナビゲーション プロパティにない場合、そのコースがナビゲーション プロパティ内のコレクションに追加されます。
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
コースのチェック ボックスが選択さていないが、そのコースが Instructor.Courses
ナビゲーション プロパティにある場合、そのコースがナビゲーション プロパティから削除されます。
else
{
if (instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Remove(course);
}
}
Views\Instructor\Edit.cshtml で、フィールドの要素の直後div
と [保存] ボタンの要素OfficeAssignment
の前div
に次のコードを追加して、チェック ボックスの配列を含む Courses フィールドを追加します。
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;
foreach (var course in courses)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
コードを貼り付けた後、改行やインデントがここで行われるように見えない場合は、ここに表示されるようにすべてを手動で修正します。 インデントは完璧である必要はありませんが、@</tr><tr>
、@:<td>
、@:</td>
、および @</tr>
の行は、示されているようにそれぞれ 1 行にする必要があります。そうしないと、ランタイム エラーが発生します。
このコードは、3 つの列を含む HTML テーブルを作成します。 各列には、チェック ボックスとその後に続くキャプションがあります。キャプションは、コース番号とタイトルから構成されます。 チェック ボックスはすべて同じ名前 ("selectedCourses") で、モデル バインダーにグループとして扱われることを通知します。 value
各チェック ボックスの属性は、ページがポストされたときのCourseID.
値に設定され、モデル バインダーは、選択されているチェック ボックスの値のみで構成CourseID
される配列をコントローラーに渡します。
チェックボックスが最初にレンダリングされると、インストラクターに割り当てられたコース用のボックスには属性がありchecked
、属性が選択されます (オンに表示されます)。
コースの割り当てを変更した後、サイトがページに戻ったときに変更を Index
確認できるようにする必要があります。 そのため、そのページのテーブルに列を追加する必要があります。 この場合、 オブジェクトを使用ViewBag
する必要はありません。表示する情報は、モデルとしてページに渡すエンティティのInstructor
ナビゲーション プロパティに既Courses
に含まれているためです。
Views\Instructor\Index.cshtml で、次の例に示すように、Office 見出しの直後に Courses 見出しを追加します。
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
次に、オフィスの場所の詳細セルの直後に新しい詳細セルを追加します。
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.Courses)
{
@course.CourseID @: @course.Title <br />
}
}
</td>
<td>
@Html.ActionLink("Select", "Index", new { id = item.ID }) |
@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>
[講師インデックス] ページを実行して、各講師に割り当てられているコースを確認します。
講師の [編集] をクリックして、[編集] ページを表示します。
いくつかのコース課題を変更し、[ 保存] をクリックします。 行った変更が Index ページに反映されます。
注: コース数が限られている場合は、講師のコース データを編集するためにここで取ったアプローチが適切に機能します。 非常に大きいコレクションの場合、別の UI と別の更新方法が必要になる場合があります。
DeleteConfirmed の更新
InstructorController.cs で、 メソッドをDeleteConfirmed
削除し、その場所に次のコードを挿入します。
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
db.Instructors.Remove(instructor);
var department = db.Departments
.Where(d => d.InstructorID == id)
.SingleOrDefault();
if (department != null)
{
department.InstructorID = null;
}
db.SaveChanges();
return RedirectToAction("Index");
}
このコードでは、次の変更が行われます。
- 講師が任意の部門の管理者として割り当てられている場合は、その部門から講師の割り当てを削除します。 このコードがないと、部署の管理者として割り当てられた講師を削除しようとすると、参照整合性エラーが発生します。
このコードでは、複数の部門の管理者として割り当てられた 1 人の講師のシナリオは処理されません。 最後のチュートリアルでは、そのシナリオが発生しないようにするコードを追加します。
オフィスの場所とコースを Create ページに追加する
InstructorController.cs で、 メソッドと HttpPost
Create
メソッドをHttpGet
削除し、代わりに次のコードを追加します。
public ActionResult Create()
{
var instructor = new Instructor();
instructor.Courses = new List<Course>();
PopulateAssignedCourseData(instructor);
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "LastName,FirstMidName,HireDate,OfficeAssignment" )]Instructor instructor, string[] selectedCourses)
{
if (selectedCourses != null)
{
instructor.Courses = new List<Course>();
foreach (var course in selectedCourses)
{
var courseToAdd = db.Courses.Find(int.Parse(course));
instructor.Courses.Add(courseToAdd);
}
}
if (ModelState.IsValid)
{
db.Instructors.Add(instructor);
db.SaveChanges();
return RedirectToAction("Index");
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
このコードは Edit メソッドで見たものと似ていますが、最初はコースが選択されていないことを除きます。 このメソッドはHttpGet
Create
、コースが選択されている可能性があるためではなく、ビュー内のループに空のコレクションをforeach
提供するためにメソッドを呼び出PopulateAssignedCourseData
します (それ以外の場合、ビュー コードは null 参照例外をスローします)。
HttpPost Create メソッドは、選択した各コースを、検証エラーをチェックするテンプレート コードの前に Courses ナビゲーション プロパティに追加し、新しい講師をデータベースに追加します。 モデル エラーが発生した場合 (たとえば、ユーザーが無効な日付を入力した場合など)、エラー メッセージでページが再表示されると、行われたコースの選択が自動的に復元されるように、モデル エラーがある場合でもコースが追加されます。
コースを Courses
ナビゲーション プロパティに追加できるようにするには、プロパティを空のコレクションとして初期化する必要があることに注意してください。
instructor.Courses = new List<Course>();
コントローラー コードでこれを行うための別の方法として、Instructor モデルでこれを行うことができます。このためには、プロパティ ゲッターを変更して、コレクションが存在しない場合に自動的に作成するようにします。次の例に示します。
private ICollection<Course> _courses;
public virtual ICollection<Course> Courses
{
get
{
return _courses ?? (_courses = new List<Course>());
}
set
{
_courses = value;
}
}
Courses
プロパティをこの方法で変更する場合、コントローラー内の明示的なプロパティの初期化コードを削除することができます。
Views\Instructor\Create.cshtml で、採用日フィールドの後と [送信] ボタンの前に、オフィスの場所のテキスト ボックスとコース チェック ボックスを追加します。
<div class="form-group">
@Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.OfficeAssignment.Location)
@Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;
foreach (var course in courses)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
コードを貼り付けた後、[編集] ページで前に行ったように改行とインデントを修正します。
[作成] ページを実行し、講師を追加します。
トランザクションの処理
基本的な CRUD 機能のチュートリアルで説明されているように、Entity Framework では既定でトランザクションが暗黙的に実装されます。 より多くの制御が必要なシナリオ (たとえば、Entity Framework の外部で実行された操作をトランザクションに含める場合) については、MSDN の 「トランザクションの操作 」を参照してください。
コードを取得する
その他のリソース
他の Entity Framework リソースへのリンクは、「 ASP.NET データ アクセス - 推奨リソース」にあります。
次のステップ
このチュートリアルでは、次の作業を行いました。
- カスタマイズされたコース ページ
- 講師ページにオフィスを追加しました
- 講師ページにコースを追加しました
- 更新された DeleteConfirmed
- [作成] ページにオフィスの場所とコースを追加しました
非同期プログラミング モデルを実装する方法については、次の記事に進んでください。
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示