[渣譯文] 使用 MVC 5 的 EF6 Code First 入門 系列:為ASP.NET MVC應用程序更新相關數據


這是微軟官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻譯,這里是第八篇:為ASP.NET MVC應用程序更新相關數據

原文:

譯文版權所有,謝絕全文轉載——但您可以在您的網站上添加到該教程的鏈接。

在之前的教程中您已經成功顯示了相關數據。在本教程中你將學習如何對相關數據進行更新。對於大多數關系,可以從主鍵或者導航屬性來進行更新。對於多對多關系,實體框架不會直接公開連接表,所以你可以從相應的導航屬性添加和移除實體。

下面的截圖顯示了你將要實現的頁面。

為課程自定義創建和編輯頁

當創建新的課程實體時,他必須擁有一個和已存在系的關系。為此,腳手架代碼創建的控制器方法及新建和編輯視圖種豆包含了用於選擇系的下拉列表。下拉列表用來設置Course.DepartmentID外鍵屬性,這對於實體框架通過Department導航屬性來加載Department實體是必須的。你將使用腳手架代碼,但需要對其做一些小的改動來增加錯誤處理和對列表內容進行排序。

在Coursecontroller.cs中,刪除之前的Create和Edit方法,並添加下面的代碼:

        private void PopulateDepartmentsDropDownList(object selectedDrpaerment = null)
        {
            var departmentsQuery = from d in db.Departments
                                   orderby d.Name
                                   select d;
            ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDrpaerment);
        }

        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)
            {
                ModelState.AddModelError("", "無法保存數據,請重試或聯系管理員。");
            }
            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,ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include="CourseID,Title,Credits,DepartmentID")]Course course)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    db.Entry(course).State = EntityState.Modified;
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
            }
            catch (RetryLimitExceededException)
            {
                ModelState.AddModelError("", "無法保存更改,請重試或聯系管理員。");
            }
            PopulateDepartmentsDropDownList(course.DepartmentID);
            return View(course);
        }

在文件的開頭增加以下引用:

using System.Data.Entity.Infrastructure;

PopulateDepartmentsDropDownList方法獲取所有的系列表並按照名稱進行排序來創建一個下拉列表。並通過ViewBag屬性傳遞到視圖上。該方法接收一個可選參數selectedDepartment,在下拉列表渲染時允許調用代碼指定被選擇的項目。視圖將傳遞DepartmentID名稱給下拉列表幫助器,然后幫助器知道應當使用DepartmentID名來在ViewBag中對象進行下拉列表的查找。

HttpGet Create方法調用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);
        }

Create和Edit的HttpPost方法還包括當出現了錯誤后,重新顯示頁面時要再設置一次所選項目的代碼:

            catch (RetryLimitExceededException)
            {
                ModelState.AddModelError("", "無法保存更改,請重試或聯系管理員。");
            }
            PopulateDepartmentsDropDownList(course.DepartmentID);
            return View(course);

這段代碼確保當頁面重新顯示錯誤信息時,已經被選擇的系保持被選擇狀態。

Course視圖已經基於系字段來使用腳手架構建了一個下拉列表。但你並不想使用系ID來作為標題,所以在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")
}

 之后在Edit視圖中進行相同的更改。

 通常腳手架不會使用主鍵來生成字段,因為主鍵值是由數據庫生成的,無法更改且對用戶顯示也沒有意義。對於課程實體腳手架代碼包含了一個用於CourseID的文本框,因為DatabaseGeneratedOption.None特性意味着用戶應當可以輸入主鍵值。但它並不明白因為該號碼只有在你想要讓其顯示在某些特定視圖中才是有意義的。所以您需要手動添加它。

在Edit視圖中,在標題字段之前添加課程編號字段。

        <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>

 

Edit視圖中已經有一個課程編號的隱藏字段(Html.HiddenFor幫助器)。為隱藏字段添加一個Html.LabelFor幫助器是沒必要的。因為它不會導致當用戶點擊保存時將課程編號包含在要發送的數據中。

在Delete和Details視圖中,更改系名稱的標題從"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>
        <dt> @Html.DisplayNameFor(model => model.Title) </dt>

 

 

勘誤注意:

之前的Layout頁面因為疏忽路由參數寫錯了,請使用下面的代碼替換布局頁面。

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - Contoso 大學</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Contoso 大學", "Index", "Home", null, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("主頁", "Index", "Home")</li>
                    <li>@Html.ActionLink("關於", "About", "Home")</li>
                    <li>@Html.ActionLink("學生", "Index", "Student")</li>
                    <li>@Html.ActionLink("教師", "Index", "Instructor")</li>
                    <li>@Html.ActionLink("課程", "Index", "Course")</li>
                    <li>@Html.ActionLink("系", "Index", "Department")</li>
                </ul>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - Contoso 大學</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>
View Code

 

以及Course的模型, public int CourseID { get; set; }的Display特性應為[Display(Name = "編號")]

如果你在看到下記日期之前就跟隨教程進行了演練,請將上面兩點更正,謝謝。

2014-5-12

運行應用程序,打開課程的創建頁面(顯示課程索引頁面並單擊創建新的)並輸入新課程的數據: 

單擊創建,課程索引頁會顯示你剛才新建的課程。同時索引頁面的洗名稱是來自導航屬性的,表示關系已經正確建立。

 

點擊編輯超鏈接來運行編輯頁。

 更改頁面上的數據並保存,檢查數據是否被正確地保存並顯示。

為講師添加編輯頁面

當您編輯一名講師的記錄時,你希望能夠更新講師的辦公室分配情況。講師實體和辦公室分配實體之間有一個一到零或一的關系。這意味着您必須處理下列情況:

  • 如果用戶清除了辦公室分配情況並且講師原來擁有一個,您必須移除並刪除這個OfficeAssignment實體。
  • 如果用戶輸入了一個辦公室並且原來講師並沒有分配,您必須新建一個OfficeAssignment實體。
  • 如果用戶更改辦公室分配值,你必須更改已經存在的OfficeAssignment實體。

打開InstructorController.cs,檢查Edit的HttpGet 方法:

        public ActionResult Edit(int? id)
        {
            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方法。用來處理辦公室分配更新:

        [HttpPost, ValidateAntiForgeryToken, ActionName("Edit")]
        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.Entry(instructorToUpdate).State = EntityState.Modified;
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
                catch (RetryLimitExceededException)
                {
                    ModelState.AddModelError("", "無法保存更改,請重試或聯系管理員");
                }
            }
            return View(instructorToUpdate);
        }

 

然后添加下列引用:

using System.Data.Entity.Infrastructure;

 

這段代碼執行了以下操作:

  • 將方法名稱變更為EditPost因為簽名現在和HttpGet方法的一樣。(依然使用ActionName特性指定的Edit URL)
  • 使用延遲加載來從數據庫中獲取當前講師實體的OfficeAssignment導航屬性。和你在HttpGet Edit方法中所做的一樣。
  • 從模型綁定器來更新檢索到的Instructor實體,使用TryUpdateModel重載允許你指定你想要包括的屬性值的白名單,這樣可以防止過多發布攻擊,如教程第二節中所述。
    if (TryUpdateModel(instructorToUpdate, "",
    new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
  • 如果辦公室位置為空,將Instructor.OfficeAssignment屬性設置為null,在OfficeAssignment表中的相關行都將被刪除。
    if (string.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
  • 將所做的更改保存到數據庫中。

在Edit視圖中,在雇佣日期字段的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>

 

 運行該頁面(選擇教師選項卡,然后點擊編輯講師),更改辦公室位置並保存。

為教師編輯頁面添加課程分配

教師能夠教授任意數量的課程。現在您會通過使用一組復選框來添加更改課程分配的功能,如下所示:

Course和Instructor實體之間的關系是多對多,這意味着您不需要直接訪問連接表中的外鍵屬性。相反,你可以從Istructor.Courses導航屬性中添加和移除實體。

UI使您能夠更改使用一組復選框來表示哪些課程是已經分配給教師的。在數據庫中的每一門課程都使用一個復選框來顯示,包括哪些已經分配給教師的。用戶可以通過選擇或清除復選框來更改課程分配。如果課程數目太多,你可能想要在視圖中使用不同的顯示數據的方法,但你會用同樣的方法來操作導航屬性以創建或刪除關系。

為了給視圖提供復選框的列表,您會使用ViewModel類,在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();
            PopulateAssignedCourseData(instructor);
            if (instructor == null)
            {
                return HttpNotFound();
            }
            return View(instructor);
        }

 private void PopulateAssignedCourseData(Instructor instructor) { var allCourse = db.Courses; var instructorCourses = new HashSet<int>(instructor.Courses.Select(c => c.CourseID)); var viewModel = new List<AssignedCourseData>(); foreach (var course in allCourse) { viewModel.Add(new AssignedCourseData { CourseID = course.CourseID, Title = course.Title, Assigned = instructorCourses.Contains(course.CourseID) }); } ViewBag.Courses = viewModel; }

 

該代碼對Courses導航屬性進行了預先加載,並且調用了一個新的PopulateAssignedCourseData方法使用AssignedCourseData視圖模型類來為復選框數組提供信息。

PopulateAssignedCourse方法中的代碼通過讀取所有Course實體並使用模型視圖類以加載列表。在每個課程中,代碼檢查講師的Courses導航屬性中是否存在該課程。為了創建一個高效的檢查一個課程是否指派給教師,已經分配的課程被放入一個HashSet集合。當課程已分配時,Assigned屬性為True。視圖會使用該屬性來確定哪些復選框應當顯示為已選定。最后,該列表作為ViewBag屬性被傳遞到視圖上。

下一步,添加用戶單擊保存時應當執行的代碼。調用一個新方法來更新Instructor實體的Courses導航屬性,使用下面的代碼替換EditPost方法,高亮部分是你進行的更改:

        [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.Entry(instructorToUpdate).State = EntityState.Modified;
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
                catch (RetryLimitExceededException)
                {
                    ModelState.AddModelError("", "無法保存更改,請重試或聯系管理員");
                }
            }
            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。

由於視圖沒有課程實體的集合,所以模型綁定器不能自動更新Courses導航屬性。不同於使用模型綁定器來更新Course導航屬性,你將在UpdateInstructorCourses方法中進行更新。因此,您需要將Course屬性從模型綁定器中排除。這不需要更改任何代碼,因為你正在使用的白名單重載列表中沒有包含Courses。

如果沒有復選框被選中,UpdateInstructorCourses中的代碼使用一個空集合來初始化Courses導航屬性。

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

 

該代碼通過循環數據庫中的所有課程,檢查哪些課程是分配給教師的來決定是否在視圖中應當選中它們。為了進行高效查找,它們都存儲在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);
                    }
                }

 

在Edit視圖中,在辦公室分配字段的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>在一行上,否則就會出現運行時錯誤。

這段代碼創建了一個HTML表格,其中包含三列。在每一列中顯示了課程的編號和標題以及一個復選框。所有的復選框都使用同一個name"selectedCourses",通知模型綁定器將它們作為一個組來進行處理。每個復選框的Value屬性被設定為CourseID的值,當頁面提交時,模型綁定器將一個僅包含了已選擇復選框的CourseID值作為數組傳遞給控制器。

復選框最初呈現時,已經分配給教師的課程會帶有checked特性,被設置為選中狀態。

在你更改課程分配后,你會想要能夠返回索引頁來驗證這些更改。因此,您需要將課程列添加到頁面的表格中。在這種情況下你不需要使用ViewBag對象,因為你想要顯示的信息已經在Instructor實體的Courses導航屬性中並作為模型傳遞給視圖了。

在Views\Instructor\Index.cshtml中,在辦公室標題后添加課程標題,如下圖所示:

<table class="table">
    <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>

 

運行應用程序,在教師索引頁上,你可以看到分配給每個教師的課程:

更改某位教師的課程分配並保存,查看更改是否已經成功保存到數據庫。

注意:這里使用復選框的方式僅針對數量有限的課程,對於更大的集合,你可能需要不同的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(); instructor.OfficeAssignment = null;
            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");
        }

 

這段代碼進行了兩處更改:

  • 當教師被刪除時,辦公室分配記錄也被刪除(如果有)。
  • 如果教師被分配作為系主任,則從該系中移除該教師。如果在沒有該段代碼的情況下你嘗試刪除以為已經被分配為系主任的教師,你會收到一個完整性錯誤。

將辦公地點和課程添加到創建頁面

在InstructorController.cs,修改HttpGet和HttpPost的Create方法,如下面代碼所示:

        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方法調用PopulateAssignedCourseData方法不是因為有可能有課程被選擇,而是為了提供一個空集合用於在視圖中循環。(否則會拋出一個空引用異常)

HttpPost的Create方法在將每個選擇的課程添加到課程導航屬性及將新教師添加到數據庫前進行錯誤檢查。在模型有錯誤(例如,用戶輸入的無效日期)時,課程不會被添加。在頁面重新顯示一條錯誤信息時,所做的任何課程選擇都會被還原。

請注意,為了能將課程添加到Courses導航屬性中,你必須初始化一個空集合:

            instructor.Courses = new List<Course>();

 

作為另一種替代方法,你可以在Course模型中修改屬性getter設置器來在它不存在時自動創建一個集合,如下面的代碼所示:

private ICollection<Course> _courses;
public virtual ICollection<Course> Courses 
{ 
    get
    {
        return _courses ?? (_courses = new List<Course>());
    }
    set
    {
        _courses = value;
    } 
}

 

如果您使用上面的方法修改了模型代碼,您可以再控制器中刪除初始化空集合的代碼。

在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>

記得在粘貼代碼后調整@:的縮進,跟之前你在Edit視圖中所做的一樣。

運行應用程序並嘗試創建一名教師。

處理事務

正如在基本CRUD功能教程中所解釋的那樣,實體框架默認會隱式地實現事務,在你需要更多的控制時,請參閱MSDN上的Working with Transactions

總結

現在你已經完成了本教程的全部相關數據。到目前為止你都是通過同步IO來進行工作的,我們會在下一節中介紹如何通過異步IO來更有效地使用服務器資源。

 

 

作者信息

  Tom Dykstra - Tom Dykstra是微軟Web平台及工具團隊的高級程序員,作家。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM