チュートリアル: ASP.NET MVC アプリで EF を使用して関連データを更新する

前のチュートリアルでは、関連データを表示しました。 このチュートリアルでは、関連データを更新します。 ほとんどのリレーションシップでは、外部キー フィールドまたはナビゲーション プロパティを更新することでこれを行うことができます。 多対多リレーションシップの場合、Entity Framework は結合テーブルを直接公開しないため、適切なナビゲーション プロパティとの間でエンティティを追加および削除します。

以下の図は、使用するページの一部を示しています。

Course_create_page

Instructor_edit_page_with_courses

コースを使用した講師の編集

このチュートリアルでは、次の作業を行いました。

  • コース ページをカスタマイズする
  • 講師ページにオフィスを追加する
  • コースを講師ページに追加する
  • 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の オブジェクトSelectListViewBag検索することを認識します。

このメソッドは HttpGetCreate 、選択した項目を設定せずに メソッドを呼び出 PopulateDepartmentsDropDownList します。これは、新しいコースでは部門がまだ確立されていないためです。

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

メソッドは HttpGetEdit 、編集中のコースに既に割り当てられている部門の 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.cshtmlViews\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 を開き、 メソッドをHttpGetEdit見てください。

{
    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 メソッドを使用して講師を選択します。

メソッドを HttpPostEdit 次のコードに置き換えます。 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 エンティティをデータベースから取得します。 これは、 メソッドで行った操作と HttpGetEdit 同じです。

  • モデル バインダーからの値を使用して、取得した 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 で、 メソッドをHttpGetEdit次のコードに置き換えます。 変更が強調表示されます。

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);
         }
      }
   }
}

メソッドシグネチャは メソッドと HttpGetEdit 異なるため、メソッド名は から EditPostEdit変更されます。

ビューにはエンティティの 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 で、 メソッドと HttpPostCreate メソッドを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 メソッドで見たものと似ていますが、最初はコースが選択されていないことを除きます。 このメソッドはHttpGetCreate、コースが選択されている可能性があるためではなく、ビュー内のループに空のコレクションを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
  • [作成] ページにオフィスの場所とコースを追加しました

非同期プログラミング モデルを実装する方法については、次の記事に進んでください。