zoukankan      html  css  js  c++  java
  • 演练5-5:Contoso大学校园管理系统5

          Contoso University示例网站演示如何使用Entity Framework 5创建ASP.NET MVC 4应用程序。 Entity Framework有三种处理数据的方式:  Database First ,  Model First , and  Code First . 本指南使用代码优先。其它方式请查询资料。 示例程序是为Contoso University建立一个网站。功能包括:学生管理、课程创建、教师分配。 本系列指南逐步讲述如何实现这一网站程序。

          如有问题,可在这些讨论区提问:  ASP.NET Entity Framework forum , the  Entity Framework and LINQ to Entities forum , or  StackOverflow.com .

    上一节完成了相关联数据的显示,本节将学习如何更新关联数据。大部分关联关系可通过更新相应的外键来完成。对于多对多关系,EF没有直接暴漏连接表,需要显式的操作导航属性(向其中添加、移除实体)来完成。

    一、定制课程的Create和Edit页面

          将要完成的效果如下:

          课程实体创建后是和某个部门有关联的。为了展示这一点,自动生成的代码生成了相应的控制器方法以及创建、编辑视图,其中包括可选择部门的下拉列表。下拉列表设置   Course.DepartmentID 外键属性,这样 EF 就可以正确加载 Department   导航属性的对应实体。这里只简单修改代码,增加错误处理和下拉列表排序功能。 
    1.在CourseController.cs中修改Edit和Create动作代码

    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 (DataException /* dex */)
       {
          //Log the error (uncomment dex variable name after DataException 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)
    {
       Course course = db.Courses.Find(id);
       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 (DataException /* dex */)
       {
          //Log the error (uncomment dex variable name after DataException 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);
    }
    
    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属性将其传递到视图。该方法有一个可选参数     selectedDepartment,以便设置下拉列表默认值。相关视图将把DepartmentID传递给DropDownList帮助器, 帮助器从ViewBag中寻找名为DepartmentIDSelectList。  

          HttpGet Create调用PopulateDepartmentsDropDownList方法时不使用默认值,因为此时还没有创建新课程数据:

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

         HttpGet Edit方法则设置默认值,因为此时课程在编辑时有原始的部门信息:

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

         HttpPost方法在捕获异常之后再次显示创建或编辑页面时,初始化下拉列表默认值:

    catch (DataException /* dex */)
       {
          //Log the error (uncomment dex variable name after DataException 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);

           代码确保如果发生异常返回页面时,原有的操作数据还在。

     2.修改Create视图

          在ViewsCourseCreate.cshtml, 在Title域之前添加代码,提供录入课程编号的编辑域。之前曾经介绍过,自动生成代码不会保护对主键的编辑域。

    model ContosoUniversity.Models.Course
    
    @{
        ViewBag.Title = "Create";
    }
    
    <h2>Create</h2>
    
    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()
        @Html.ValidationSummary(true)
    
        <fieldset>
            <legend>Course</legend>
    
            <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>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Title)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Credits)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Credits)
                @Html.ValidationMessageFor(model => model.Credits)
            </div>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.DepartmentID, "Department")
            </div>
            <div class="editor-field">
                @Html.DropDownList("DepartmentID", String.Empty)
                @Html.ValidationMessageFor(model => model.DepartmentID)
            </div>
    
            <p>
                <input type="submit" value="Create" />
            </p>
        </fieldset>
    }
    
    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>
    
    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
    }

          同样地修改ViewsCourseEdit、Delete和Details视图,在Title字段前添加course number字段。

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

          运行Create、Edit、Delete、Details,查看效果。

     

    二、为教师创建Edit页面

        

          当你编辑一条教师记录时,你可能希望能够更新教师的办公地点。Instructor实体和OfficeAssignment之间是一对一的关系,所以必须处理以下情况:

    • 如果用户清除了办公地点,而它本来是有值的,那么你必须删除OfficeAssignment实体。
    • 如果用户输入办公地点值,而它本来是空的,那么你必须新建一个OfficeAssignment实体。
    • 如果用户改变了办公地点的值,那么你必须改变已有的OfficeAssignment实体值。

    1.添加办公地点

          Instructor控制器中,自动生成的HttpGet Edit方法代码如下:

    public ActionResult Edit(int id = 0)
    {
        Instructor instructor = db.Instructors.Find(id);
        if (instructor == null)
        {
            return HttpNotFound();
        }
        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)
            .Where(i => i.InstructorID == id)
            .Single();
        return View(instructor);
    }

          使用eager loading贪婪加载方式获取OfficeAssignment实体,就不能使用Find方法,所以我们使用了Where。

          将HttpPost Edit方法替换为如下代码。

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(int id, FormCollection formCollection)
    {
       var instructorToUpdate = db.Instructors
           .Include(i => i.OfficeAssignment)
           .Where(i => i.InstructorID == 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 (DataException /* dex */)
          {
             //Log the error (uncomment dex variable name after DataException 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.");
          }
       }
       ViewBag.InstructorID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", id);
       return View(instructorToUpdate);
    }

          这部分代码的作用是:  

    • 从数据库通过贪婪加载获取InstructorOfficeAssignment实体。这是和自动生成的HttpGet Edit方法一样的。

    • 使用模型绑定器数据更新Instructor实体。

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    • 如果办公室信息为空,将Instructor.OfficeAssignment属性设为null,OfficeAssignment表中相应的记录也将删除。
    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    • 保存对数据库的修改。

          在ViewsInstructorEdit.cshtmlHire Date div标记之后, 添加办公室信息的编辑域:

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

          运行,测试效果。

     

    2.添加教师讲授的课程数据更新

          在教师Edit页面,增加对教师承担课程编辑的功能,效果如下。

          Course和Instructor实体之间是多对过关系,我们并没有手动生成它们之间的连接表,自动生成的连接表无法直接访问。事实上,我们将通过在Instructor.Courses导航属性中,添加或删除关联实体的方式来实现关系的维护。

          改变老师承担课程的功能,是通过一组复选框来实现。列出数据库中所有课程,教师承担某课程,则该复选框选中。用户通过选中或者取消选中的操作修改课程的分配情况。如果课程数目很多,你可能希望使用别的显示方法,但操作导航属性来添加或删除关系的方法是一样的。  

          为了能够显示这一组复选框,我们将使用视图模型类。在Models文件夹中,创建AssignedCourseData.cs文件。

    namespace ContosoUniversity.ViewModels
    {
        public class AssignedCourseData
        {
            public int CourseID { get; set; }
            public string Title { get; set; }
            public bool Assigned { get; set; }
        }
    }

          更新Instructor控制器的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;
    }

          代码使用贪婪模式加载Courses导航属性,调用PopulateAssignedCourseData方法实现为视图提供AssignedCourseData视图模型的数据。  

          PopulateAssignedCourseData方法读取所有Course实体。对每一个Courses检查是否已经存在于某教师的导航属性中。为了提高效率,将当前承担课程的ID形成一个HashSet 集合。教师承担某课程,则Assigned属性将设为 true。视图将使用此属性决定哪些选择框处于被选中状态。最后通过ViewBag的一个属性将列表传递到视图。

          下一步,完成保存代码。使用如下代码替换HttpPost Edit方法的代码,调用一个新的方法更新Instructor实体的Courses导航属性。

    [HttpPost]
    [ValidateAntiForgeryToken]
    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, "", 
          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 (DataException /* dex */)
          {
             //Log the error (uncomment dex variable name after DataException 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);
             }
          }
       }

           因为自动生成的视图中,不包含Course实体集合, 因此模型绑定器不能直接更新Courses导航属性。更新由UpdateInstructorCourses方法完成。因此要把Courses属性从模型绑定器中排除出去。这并不需要修改TryUpdateModel的代码,因为使用了白名单,Courses不在名单之内。

          如果没有选中任何课程,UpdateInstructorCoursesCourses导航属性设为一个空的列表:

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

          代码执行循环检查数据库中的每一课程,若此课程被选中则判断是否已经包含在相关数据中,如果没有则添加到导航属性。为了提高效率,把选中课程 Id和已有课程ID放在哈希表中。

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

          在ViewsInstructorEdit.cshtml文件中,在OfficeAssignment之后,添加课程复选框。

    @model ContosoUniversity.Models.Instructor
    
    @{
        ViewBag.Title = "Edit";
    }
    
    <h2>Edit</h2>
    
    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()
        @Html.ValidationSummary(true)
    
        <fieldset>
            <legend>Instructor</legend>
    
            @Html.HiddenFor(model => model.InstructorID)
                 
    
            <div class="editor-label">
                @Html.LabelFor(model => model.LastName)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.LastName)
                @Html.ValidationMessageFor(model => model.LastName)
            </div>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.FirstMidName)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.FirstMidName)
                @Html.ValidationMessageFor(model => model.FirstMidName)
            </div>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.HireDate)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.HireDate)
                @Html.ValidationMessageFor(model => model.HireDate)
            </div>
    
            <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>
    
            <div class="editor-field">
        <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>
            <p>
                <input type="submit" value="Save" />
            </p>
        </fieldset>
    }
    
    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>
    
    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
    }

          这段代码创建了一个包含三列的表格。每一列包含一个复选框、课程编号和名称。所有复选框的名字都是一样的 ("selectedCourses"), 模型绑定器由此得知将其作为一组信息来处理。复选框的 value设为对于课程的 CourseID。当编辑提交之后,模型绑定器将被选中的复选框的值组合为一个数组传给控制器。

          更新ViewsInstructorIndex.cshtml视图,在Office列之后添加Courses,更新Details视图。

    @model ContosoUniversity.ViewModels.InstructorIndexData
    
    @{
        ViewBag.Title = "Instructors";
    }
    
    <h2>Instructors</h2>
    
    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    <table>
        <tr>
            <th></th>
            <th>Last Name</th>
            <th>First Name</th>
            <th>Hire Date</th>
            <th>Office</th>
            <th>Courses</th>
        </tr>
        @foreach (var item in Model.Instructors)
        {
            string selectedRow = "";
            if (item.InstructorID == ViewBag.InstructorID)
            {
                selectedRow = "selectedrow";
            } 
            <tr class="@selectedRow" valign="top">
                <td>
                    @Html.ActionLink("Select", "Index", new { id = item.InstructorID }) | 
                    @Html.ActionLink("Edit", "Edit", new { id = item.InstructorID }) | 
                    @Html.ActionLink("Details", "Details", new { id = item.InstructorID }) | 
                    @Html.ActionLink("Delete", "Delete", new { id = item.InstructorID })
                </td>
                <td>
                    @item.LastName
                </td>
                <td>
                    @item.FirstMidName
                </td>
                <td>
                    @String.Format("{0:d}", item.HireDate)
                </td>
                <td>
                    @if (item.OfficeAssignment != null)
                    { 
                        @item.OfficeAssignment.Location  
                    }
                </td>
                <td>
                    @{
                    foreach (var course in item.Courses)
                    {
                        @course.CourseID @:  @course.Title <br />
                    }
                    }
                </td>
            </tr> 
        }
    </table>
    
    @if (Model.Courses != null)
    { 
        <h3>Courses Taught by Selected Instructor</h3> 
        <table>
            <tr>
                <th></th>
                <th>ID</th>
                <th>Title</th>
                <th>Department</th>
            </tr>
    
            @foreach (var item in Model.Courses)
            {
                string selectedRow = "";
                if (item.CourseID == ViewBag.CourseID)
                {
                    selectedRow = "selectedrow";
                } 
            
                <tr class="@selectedRow">
    
                    <td>
                        @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
                    </td>
                    <td>
                        @item.CourseID
                    </td>
                    <td>
                        @item.Title
                    </td>
                    <td>
                        @item.Department.Name
                    </td>
                </tr> 
            }
    
        </table> 
    }
    @if (Model.Enrollments != null)
    { 
        <h3>Students Enrolled in Selected Course</h3> 
        <table>
            <tr>
                <th>Name</th>
                <th>Grade</th>
            </tr>
            @foreach (var item in Model.Enrollments)
            { 
                <tr>
                    <td>
                        @item.Student.FullName
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Grade)
                    </td>
                </tr> 
            }
        </table> 
    }

           运行Instructor Index,查看效果。点击Edit查看功能,修改一些课程的分配,然后点击Save,修改结果在Index页面展示。

          这种方式在课程数目不多时有效。如果课程数目很多需要修改显示方式和更新方法。

    3.更新Instructor的Delete方法

          修改代码,当删除教师时,为其分配的办公室信息随之删除:

     [HttpPost, ActionName("Delete")]
            [ValidateAntiForgeryToken]
            public ActionResult DeleteConfirmed(int id)
            {
                Instructor instructor = db.Instructors
                  .Include(i => i.OfficeAssignment)
                  .Where(i => i.InstructorID == id)
                  .Single();
     
                foreach (var department in db.Departments)
                {
                    if (department.InstructorID == id)
                    {
                        department.InstructorID = null;
                    }
                }
                db.Instructors.Remove(instructor);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

        

          已经完成了完整的CRUD操作,但没有处理同步问题。下一节将引入同步问题,介绍处理方法,为CRUD操作添加同步处理。

    视频教程: 12

  • 相关阅读:
    CNN的学习记录
    softmax和softmax loss的学习记录
    Vue2.0 生命周期
    Vue methods 方法
    Vue2.0 全局操作 Vue.set
    Vue2.0 自定义指令 vuedirective
    Vue2.0 构造器的延伸 Vue.extend
    vue computed
    vuecli 脚手架分析
    h5表单介绍与案例
  • 原文地址:https://www.cnblogs.com/meetyy/p/4046056.html
Copyright © 2011-2022 走看看