トレーニングコース。 ASP.NET MVCアプリケーションでEntity Frameworkを使用して関連データを更新する

これは、Entity FrameworkおよびASP.NET MVC 3開発記事シリーズの拡張機能であり、最初の章は次のリンクにあります。

前のレッスンでは、データを表示しました。 次に、それらを更新します。 ほとんどの関係では、外部キーを使用して関連データを更新できます。 多対多の関係の場合、EFは直接結合されたテーブルを使用しないため、対応するナビゲーションプロパティからエンティティを手動で追加および削除する必要があります。



結果は図に示されています。



clip_image001clip_image002clip_image003



コースの作成および編集ページの編集



コースの新しいエッセンスを作成するときは、既存の教員と接続する必要があります。 これを確実にするために、生成されたコードには、コントローラーメソッドと、教員を強調表示するドロップダウンリスト付きの作成および編集ビューが含まれています。 ドロップダウンリストは、外部キープロパティCourse.DepartmentIDを定義します。EFは、Departmentナビゲーションプロパティに対応するDepartmentエンティティをロードするために必要なすべてのものです。 生成されたコードを少し修正して使用し、エラーを処理し、ドロップダウンリストのアイテムを並べ替えます。



CourseController.csで 、EditおよびCreateメソッドのコードを置き換えます。



public ActionResult Create() { PopulateDepartmentsDropDownList(); return View(); } [HttpPost] public ActionResult Create(Course course) { try { if (ModelState.IsValid) { db.Courses.Add(course); db.SaveChanges(); return RedirectToAction("Index"); } } catch (DataException) { //Log the error (add a variable name after DataException) 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) { Course course = db.Courses.Find(id); PopulateDepartmentsDropDownList(course.DepartmentID); return View(course); } [HttpPost] public ActionResult Edit(Course course) { try { if (ModelState.IsValid) { db.Entry(course).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } } catch (DataException) { //Log the error (add a variable name after DataException) ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator."); } PopulateDepartmentsDropDownList(course.DepartmentID); return View(course); } 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); }
      
      





PopulateDepartmentsDropDownListメソッドは、名前でソートされたすべての部門のリストをロードし、ドロップダウンリストのSelectListコレクションを作成し、ViewBagプロパティのビューにコレクションを渡します。 このメソッドは、呼び出し元がオプションでデフォルト項目を決定できるようにするパラメーターを受け入れます。



HttpGet createメソッドは、選択した項目を指定せずにPopulateDepartmentsDropDownListメソッドを呼び出します。これは、教員の新しいコースがまだ指定されていないためです。



 public ActionResult Create() { PopulateDepartmentsDropDownList(); return View(); }
      
      





HttpGet Editメソッドは、編集可能なコースに関連付けられた教員IDに基づいて、選択されたアイテムを決定します。



 public ActionResult Edit(int id) { Course course = db.Courses.Find(id); PopulateDepartmentsDropDownList(course.DepartmentID); return View(course); }
      
      





CreateおよびEditのHttpPostメソッドには、同様のコードが含まれています。



 catch (DataException) { //Log the error (add a variable name after DataException) ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator."); } PopulateDepartmentsDropDownList(course.DepartmentID); return View(course);
      
      





このコードを使用すると、ページを更新してエラーメッセージを表示したときに、以前に選択した教員が選択されたままになるようにすることができます。



ビュー \ コース \ 作成で cshtmlは、 タイトルフィールドの前に新しいフィールドを追加して、ユーザーがコース番号を入力します。 主キーのプロパティはビューで生成されないが、この場合、主キーには意味があるため、その値を入力する機会をユーザーに与える必要があると以前に説明しました。



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





Views \ Course \ Edit.cshtmlViews \ Course \ Delete.cshtml 、およびViews \ Course \ Details.cshtmlで、 タイトルフィールドの前に新しいフィールドを追加して、コース番号を表示します。 これは主キーであるため、表示する必要がありますが、編集は禁止する必要があります。



 <div class="editor-label"> @Html.LabelFor(model => model.CourseID) </div> <div class="editor-field"> @Html.DisplayFor(model => model.CourseID) </div>
      
      





プロジェクトを実行し、 作成ページに移動して、新しいコースのデータを入力します。



clip_image001[1]



作成をクリックします 。 コースインデックスページに、追加されたコースのリストが表示されます。 教員の名前はナビゲーションプロパティから取得されるため、エンティティ間の接続が正しく確立されていることが証明されます。



clip_image004



編集ページを開きます(コースインデックスページを開き、コースの[ 編集 ]をクリックします)。



clip_image002[1]



データを変更し、[保存]をクリックします。 更新されたコースデータを含むコースインデックスページが表示されます。



インストラクター用の編集ページの追加



教師に関するレコードを編集するとき、彼のオフィスに関するレコードも更新できます。 インストラクターエンティティは、OfficeAssignmentに1対0または1対1で関連付けられます。つまり、次の状況を処理する必要があります。

InstructorControllerで cs HttpGet Editメソッドに注意してください:



 public ActionResult Edit(int id) { Instructor instructor = db.Instructors.Find(id); ViewBag.InstructorID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.InstructorID); return View(instructor); }
      
      





生成されたコードは私たちには適していません-ドロップダウンリストのデータを初期化しますが、テキストフィールドが必要なので、このコードを次のように置き換えます



 public ActionResult Edit(int id) { Instructor instructor = db.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .Where(i => i.InstructorID == id) .Single(); return View(instructor); }
      
      





このコードはViewBagを使用せず、関連するOfficeAssignmentおよびCourseエンティティの積極的な読み込みを定義します。 (コースは後で必要になります。)Findメソッドに積極的な読み込みを定義することはできないため、代わりにWhereメソッドとSingleメソッドを使用して教師を選択します。



HttpPost Editメソッドのコードを、オフィスエントリの編集を処理する次のコードに置き換えます。



 [HttpPost] public ActionResult Edit(int id, FormCollection formCollection) { var instructorToUpdate = db.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .Where(i => i.InstructorID == id) .Single(); if (TryUpdateModel(instructorToUpdate, "", null, new string[] { "Courses" })) { try { if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location)) { instructorToUpdate.OfficeAssignment = null; } db.Entry(instructorToUpdate).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } catch (DataException) { //Log the error (add a variable name after DataException) ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator."); return View(); } } return View(instructorToUpdate); }
      
      





このコードは次の機能を実行します。



 if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location)) { instructorToUpdate.OfficeAssignment = null; }
      
      





ビュー \ インストラクター \ 編集で Hire Dateフィールドのdivコンテナの後にcshtmlで 、オフィスの住所を表示する新しいフィールドを追加します。



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





[ 講師 ]タブを選択し、教師の[ 編集 ]をクリックします。



clip_image005



オフィスの 場所の値を変更し、[ 保存 ]をクリックします



clip_image006



[インデックス]ページに新しいアドレスが表示されますサーバー エクスプローラーで OfficeAssignmentテーブルを開くと、テーブルにエントリが表示されます



clip_image007



[編集]ページに戻り、[ オフィスの 場所 ]をクリアして、[ 保存 ]をクリックします 。 [インデックス]ページには空白のアドレスが表示され、 サーバー エクスプローラーにはレコードが削除されたことが表示されます。



clip_image008



[ 編集]ページで、[ オフィスの 場所 ]に新しい値を入力し、[ 保存 ]をクリックします 。 [ インデックス]ページに新しいアドレス値が表示され、 サーバー エクスプローラーに新しいエントリが表示されます。



clip_image009



講師の編集ページにコースの割り当てを追加する



教師はコースを無制限に教えることができます。 コースに割り当てる機能を追加して、インストラクター編集ページを更新します。



clip_image003[1]



CourseとInstructorの関係は多対多として定義されているため、結合テーブルや外部キーへのアクセスはありません。 代わりに、Instructor.Coursesナビゲーションプロパティを操作する必要があります。



教師へのコースのバインドを変更する機能を提供するインターフェイスは、チェックボックスグループに囲まれています。



データをビューに転送してチェックボックスグループを生成するには、ビューモデルクラスを使用する必要があります。 ViewModelsフォルダーに次の内容のAssignedCourseData.csを作成します。



 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; 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) { Instructor instructor = db.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .Where(i => i.InstructorID == id) .Single(); 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; }
      
      





新しいメソッドのコードは、すべてのCourseエンティティをロードして、コースのリストを作成します。 各コースについて、インストラクターのコースナビゲーションプロパティに存在のチェックがあります。 コースが教師に関連付けられているかどうかを効果的に調べるために、教師関連のコースがHashSetコレクションに配置されます。 割り当てられたコースの割り当てられたコースプロパティはtrueに設定されます。 ビューはこのプロパティを使用して、チェックボックスをオンにするかどうかを決定します。 その後、リストはViewBagプロパティでビューに渡されます。



[保存]ボタンハンドラコードを追加します。HttpPostEditメソッドコードを、InstructorエンティティのCoursesナビゲーションプロパティを更新する新しいメソッドを呼び出すコードに置き換えます。



 [HttpPost] public ActionResult Edit(int id, FormCollection formCollection, string[] selectedCourses) { var instructorToUpdate = db.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .Where(i => i.InstructorID == id) .Single(); if (TryUpdateModel(instructorToUpdate, "", null, new string[] { "Courses" })) { try { UpdateModel(instructorToUpdate, "", null, new string[] { "Courses" }); if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location)) { instructorToUpdate.OfficeAssignment = null; } UpdateInstructorCourses(selectedCourses, instructorToUpdate); db.Entry(instructorToUpdate).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } catch (DataException) { //Log the error (add a variable name after DataException) 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); } } } }
      
      





チェックボックスが選択されていない場合、UpdateInstructorCoursesのコードは、空のコレクションでCoursesナビゲーションプロパティを初期化します。



 if (selectedCourses == null) { instructorToUpdate.Courses = new List(); return; }
      
      





コードはデータベース内のすべてのコースレコードを反復処理し、コースのチェックボックスがオンになっているが、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で 、OfficeAssignmentのdivコンテナーの直後にチェックボックスグループを持つCoursesフィールドを追加します。



 <div class="editor-field"> <table style="width: 100%"> <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>
      
      





このコードは、3つの列のHTMLテーブルを作成します。各列には、コース番号と名前の見出しが付いたチェックボックスが含まれています。 すべてのチェックボックスには同じ名前(「selectedCourses」)があり、同じグループに属することを象徴しています。 各チェックボックスの値属性はCourseIDに設定されています。 ページがデータを送信すると、選択されたチェックボックスとCourseID値で構成される配列がコントローラーに渡されます。



チェックボックスが生成されると、すでに教師に割り当てられているものにチェック属性があります。



教師へのコースバインドを変更した後、インデックスページに戻るときに変更を確認する必要があります。 これを行うには、このページのテーブルに列を追加します。 これを行うには、ViewBagオブジェクトを使用する必要はありません。表示する必要がある情報は、モデルとしてビューに渡されるInstructorエンティティのCoursesナビゲーションプロパティに既にあるためです。



Views \ Instructor \ Index.cshtmlで 、ヘッダーセル<th> Courses </ th>を<th> Office </ th>の直後に追加します。



 <tr> <th></th> <th>Last Name</th> <th>First Name</th> <th>Hire Date</th> <th>Office</th> <th>Courses</th> </tr>
      
      





その後、オフィスの住所を持つセルの直後に新しいセルを追加します。



 <td> @{ foreach (var course in item.Courses) { @course.CourseID @:  @course.Title <br /> } } </td>
      
      





プロジェクトを実行し、 インストラクター インデックスページに移動します。



clip_image010



教師の[ 編集]をクリックします。



clip_image003[2]



コースのバインドを変更し、[保存]をクリックします。 変更は[インデックス]ページに表示されます。



納入品を使用した作業のレッスンの紹介が完了しました。 単純なCRUD操作を完了しましたが、同時実行性の問題には対処しませんでした。 次のレッスンでは、並列処理のトピック、それを扱う問題に専念します。



謝辞


Alexander Belotserkovskyの翻訳にご協力いただきありがとうございます。



All Articles