zoukankan      html  css  js  c++  java
  • 【EntityFramework系列教程六,翻译】在ASP.NET MVC程序中使用EntityFramework对相关数据进行更新

    前一章你已经学会如何显示相关数据,在本章中你将学会如何更新相关数据。大部分情况下更新只需通过更新对应的外键字段即可完成,不过对“多对多”关系而言,由于EF不是直接暴露那个中间连接表,因此你不得不“显式”从对应的导航属性中增加或者删除实体得以完成。

    以下一些截图是你今日要完成的任务:

    Course_create_page

    Course_edit_page

    Instructor_edit_page_with_courses

    【为Courses自定义“新增”和“编辑”页面】

    当一个新课程创建之时,它总是隶属于某一个特定的系;为方便期间,自生成“创建”和“编辑”的代码架构中就包含了一个可供选择“系”的下拉列表。下拉框设置了Department的Id,这是所有EntityFramework实体都有的,为了把正确的Department加载到Course的Department导航属性中去。你只需对此代码做一些小小的变动(增加错误捕获以及对下拉列表框中的“系”排序)即可使用,代码如下:

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

    “PopulateDepartmentDropdownList”方法获取了所有根据名称排序的课程,并且创建了一个SelectList列表作为Dropdownlist的数据源赋值给它,最后把整个SelectList存入ViewBag中。此方法另外还接受一个可选参数,以便下拉列表回发后它可以被置初值。

    HttpGet传递方式的Create方法没有调用带参数的PopulateDepartmentDropdownlist方法,原因在于当一个课程刚被创建之时,“系”尚未确定。

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

    对于以HttpPost方式传递的Create和Edit方法都包含了“在页面重现后,设定对应课程的”代码段,其位置位于错误捕获之后:

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

    此代码确保当页面回发后再次呈现之时,保留原先选择的课程不变,同时显示错误信息。

    在“Views\Course\Create.cshtml”中在Title字段之前添加一个可供读者输入课程Id的字段。正如先前所说的一样,自生成的代码不会包含主键字段,但是这个字段在此处有用,因为这样可以输入课程的Id。

    <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”两个页面中Title字段之前添加一个新字段用以显示课程编号,考虑是主键所以只能显示,不能被修改。

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

    运行Create页面(显示课程索引页,点击“Create New”),为创建新课程录入数据:

    Course_create_page

    点击Create,随着新课程的加入Course索引页加载显示完毕。在索引页中“系”名称自导航属性而来,说明表关系已经正确创建。

    Course_Index_page_showing_new_course

    运行“Edit”页面(显示课程索引页),点击Edit按钮:

    Course_edit_page

    在页面上更改一些数据并点击Save,课程索引页就显示已经更新的课程信息。

    【为Instructors增加编辑页】

    当你要编辑Instructor实体的时候,你应当可以更新OfficeAssignment。由于Instructor和OfficeAssignment之间是“一对一”或者“一对零”的关系,那么你必须要处理下列一些情况:

    1)当一个用户清除了OfficeAssignment信息时,你应当移除此实体。

    2)当一个用户添加了OfficeAssignment信息,且原来为空时,你应当为此创建一个实体。

    3)当一个用户更改了OfficeAssignment信息时,你应当更新对应的实体。

    打开InstructorController.cs文件看看HttpGet方式的“编辑”方法:

    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保存并回传数据,不过使用了“饥饿模式”对Course和OfficeAssignment”进行数据加载(目前暂且用不到Course,不过稍后会用到的)。“饥饿模式”状态下你不能用Find寻找数据,取而代之的是使用“Where”和“Single”。

    用以下代码替换HttpPost方式的Edit方法,它用于专门处理OfficeAssignment的更新:

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

    此代码做了一些事情:

    1)使用“饥饿模式”在获取当前的Instructor实体同时加载了OfficeAssignment和Course等信息,这和你在HttpGet模式中的Edit方法一样。

    2)从模型绑定设置中更新已获取的Instructor实体,此不包括Courses导航属性。

    If (TryUpdateModel(instructorToUpdate, "", null, new string[] { "Courses" }))

    3)第二个和第三个参数分别表示了“属性名称上无前缀”以及“没有需要包含的属性列表”,如果验证失败,那么TryUpdateModel将返回false,代码最终也执行return View这块。如果Office的Location为空,那么把对应的OfficeAssignment设置为null,这样相关的数据自然就被删除了。

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

    4)保存更新的记录:在“”中,在Hire Date字段的div后,为编辑Office的Location添加新字段:

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

    运行该页面(选择Instrcutors选项卡,单击Edit):

    Instructor_edit_page

    变更Location中的数据,点击保存:

    Changing_the_office_location

    那么新的Location数据就在Index页面显示出来,在Server Explorer中你也确实可以看到OfficeAssignment表中发生了同样的改变:

    Server_explorer_showing_changed_office_location

    返回到Edit页,清空OfficeAssignment中的内容点击Save,Index显示空白的Office的Location;与之对应数据表中也删除了该记录:

    Server_explorer_showing_deleted_office_location

    再次到Edit页,为Office的Location数据做一些变更,单击Save之后看到Index页中数据同样做了变更,数据表显示的记录也表示数据发生了更新:

    Server_explorer_showing_added_office_location

    【为Instructor页添加“赋予课程”功能】

    教师们可能教授多门课程,现在你借助一组复选框为Instructor增加了选课的功能。如下所示:

    Instructor_edit_page_with_courses

    Course和Instructor之间是“多对多”的关系,所以你不能简单地访问“连接中间表”或者是外键;你可以从Instructors.Courses中增加或者移除相关的课程。

    使得你可以任意选择教授课程的用户界面其实是一组复选框——每一个课程在界面上对应一个复选框,如果老师教授某课程,则那个对应复选框则打勾;我们可以通过打勾或者取消打勾的方式任意为某个老师指定课程,如果课程过于多的话,你或许要考虑换个界面展示这些课程;不过为创建或者删除相关的课程,你还是使用和本示例同样的方法。

    为了给这一堆复选框提供数据,你需要使用到视图模型类;在ViewModel文件夹中创建“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;
    }

    新方法中从数据库读取全部的课程信息,以便使用视图模型类加载此系列的课程信息;对于每个课程而言,代码将检测对应的Instructor中导航属性Courses是否包含该课程:为了提高效率寻找,某个Instructor所有的课程都被放入一个HashSet,并且那些已选择的课程“Assigned”属性被设置成true。视图将决定哪些复选框将是处于勾选状态的……同时,这些信息将被存储到ViewBag并且发送回页面视图中。

    下一步,添加点击“Save”按钮时要执行的代码——使用以下代码替换HttpPost方式的Edit方法,它调用了一个新方法用来更新指定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
            {
                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导航属性自动加入此Instructor:

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

    此代码创建了有着3列的表,每一列中都有一个复选框,旁边附带课程Id和名称;这些复选框的名称都是一致的,这告知模型绑定机制——它们属于同一组!每个复选框的value属性绑定了一个课程Id,这样当页面提交时,只有打勾的id才会被上传。

    当这些复选框回发之后,对于那些已经被赋给当前Instructor的所有课程自然处于打勾状态。

    在对教师课程信息选择做出变更之后,当页面回到Index后你应当可以检测这些变化;因此你需要在那个表中额外增加一个字段——此情况下你不必使用ViewBag,因为这些信息都已被你传递给页面的模型实体Instructor中的Courses这个导航属性所包含了。

    在“Views\Instructor\Index.cshtml”中,在“<th>Office</th>”之后加上“<th>Courses</th>”:

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

    随后紧挨着office location单元格下增加一个新的单元格,用于显示全部选定课程:

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

    现在运行Instructor的Index页看看所有教员,以及他们任教的课程:

    Instructor_index_page

    点击某个教员的“Edit”查看Edit页面信息:

    Instructor_edit_page_with_courses

    对一些课程选择信息做一些改变,然后单击“Save”,变更信息将立即在Instructor的Index页面上反映出来。

    目前为止你已经完成了处理相关数据的任务,并且直到本章节,连同先前所有教程在一起,它们教会你完成了整个增删改查的任务;不过你尚未处理“并发冲突”问题。下一章我们就要讨论此问题,并且为你的一个已经完成的实体类型增加此功能,并加以解释。

    关于其它EntityFramework资源您可以本系列最后一篇末尾处找到。

  • 相关阅读:
    Java基础50道经典练习题(23)——求岁数
    Java基础50道经典练习题(22)——递归求阶乘
    团队第一阶段冲刺04
    团队第一阶段冲刺03
    团队第一阶段冲刺02
    团队第一阶段冲刺01
    软件工作进度01
    软团队项目01之电梯演讲视频
    团队项目1
    C语言动静态链接库使用(笔记)
  • 原文地址:https://www.cnblogs.com/ServiceboyNew/p/2440068.html
Copyright © 2011-2022 走看看