📜 ⬆️ ⬇️

Training course. Updating related data using the Entity Framework in an ASP.NET MVC application

This is an extension of a series of articles devoted to developing with the Entity Framework and ASP.NET MVC 3. You can find the first chapters at the following links:
In previous lessons we displayed the data. Now you will update them. For most links, you can update the associated data using foreign keys. For a many-to-many relationship, EF does not directly use the joined table, so you must manually add and remove entities from the corresponding navigation properties.

The results are shown in the illustrations.

clip_image001clip_image002clip_image003
')

Editing the Create and Edit pages for Courses


When creating a new entity course, it must be associated with an existing faculty. To ensure this, the generated code includes a controller method and a Create and Edit view with drop-down lists to highlight the faculty. The drop-down list defines the foreign key property Course.DepartmentID, all that the EF needs for loading the Department of navigation property with the corresponding Department entities. You will use the generated code with a few changes to handle errors and sort the items in the drop-down list.

In CourseController.cs, replace the code for the Edit and Create methods:

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

The PopulateDepartmentsDropDownList method loads a list of all departments, sorted by name, creates a SelectList collection for the drop-down list and passes the collection to a view in the ViewBag property. The method accepts a parameter that allows the caller to optionally determine the element selected by default.

The HttpGet create method calls the PopulateDepartmentsDropDownList method without specifying the selected element, since a new course for the faculty has not yet been specified:

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

The HttpGet Edit method determines the selected item based on the Faculty ID associated with the course being edited:

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

The methods HttpPost for Create and Edit include similar code:

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

The code allows you to make sure that when you refresh the page to display an error message, the previously selected faculty will remain selected.

In Views \ Course \ Create . cshtml add a new field in front of the Title field for the user to enter the course number. Earlier it was explained that the properties of primary keys are not generated on the presentation, but in this case the primary key carries the meaning, therefore, it is necessary to give the user the opportunity to enter its value.

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

In Views \ Course \ Edit.cshtml , Views \ Course \ Delete.cshtml , and Views \ Course \ Details.cshtml , add a new field in front of the Title field to display the course number. Since this is a primary key, it should be displayed, but editing should be prohibited.

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

Run the project and go to the Create page and enter the data for the new course:

clip_image001[1]

Click Create . The Course Index page appears with a list of the added course. The name of the faculty will be taken from the navigation property, thus it will be verified that the connection between the entities is established correctly.

clip_image004

Open the Edit page (open the Course Index page and click Edit on the course).

clip_image002[1]

Change the data and click Save. The Course Index page is displayed with updated course data.

Adding the Edit Page for Instructors


When you edit a teacher record, you can also update a record about his office. The Instructor entity is associated with OfficeAssignment as one-to-zero-or-to-one, which means that the following situations need to be handled:
In InstructorController . cs note the HttpGet Edit method:

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

The generated code does not suit us - it initializes the data for the drop-down list, but we need a text field, so replace this code with:

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

This code does not use ViewBag and defines eager loading for related OfficeAssignment and Course entities. (Courses will be needed later.) For the Find method, eager loading cannot be defined, so instead, the Where and Single methods are used to select the teacher.

Replace the code for the HttpPost Edit method with the following code that handles the editing of an office record:

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

This code performs the following functions:

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

In Views \ Instructor \ Edit . cshtml after the div containers for the Hire Date field, add a new field to display the office address:

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

Select the Instructors tab and click Edit on the teacher.

clip_image005

Change the Office Location value and click Save .

clip_image006

A new address will appear on the Index page, and you will see a record in the table when you open the OfficeAssignment table in Server Explorer .

clip_image007

Go back to the Edit page, clear the Office Location and click Save . The Index page will display an empty address and Server Explorer will display that the entry has been deleted.

clip_image008

On the Edit page, enter a new value in Office Location and click Save . On the Index page, the new value of the address appears, and Server Explorer displays the appearance of the new entry.

clip_image009

Adding Course Assignments to the Instructor Edit Page


Teachers can conduct an unlimited number of courses. You will update the Instructor Edit page by adding the possibility of assigning to the course:

clip_image003[1]

The relationship between Course and Instructor is defined as many-to-many, so there is no access to the joined table or foreign keys. Instead, it is necessary to operate the Instructor.Courses navigation property.

The interface, which provides the ability to change the binding of courses to teachers, is enclosed in the group of check boxes.

To transfer data to a view, to generate a group of check boxes, you must use the view model class. Create AssignedCourseData.cs in the ViewModels folder with the following contents:

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

In InstructorController . cs , in the HttpGet Edit method, call a new method that will provide the presentation with information for generating the check boxes group:

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

The code of the new method loads all the entities of the Course to create a list of courses. For each course, there is a check for existence in the instructor's Courses navigation property. To effectively find out if a course is related to a teacher, courses related to a teacher are placed in the HashSet collection. The Assigned course property for assigned courses is set to true. The view uses this property to determine whether to check a check box or not. After that, the list is passed in the ViewBag property to the view.

Add the code for the Save button handler: replace the HttpPost Edit method code with the code that calls the new method that updates the Courses navigation property for the Instructor entity.

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

If no check boxes are selected, the code in the UpdateInstructorCourses initializes the Courses navigation property to an empty collection:

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

The code loops through all the course records in the database, and if the check box for the course is checked, but the course is not in the Instructor. Courses navigation property, the course is added to the collection in the navigation property.

 if (selectedCoursesHS.Contains(course.CourseID.ToString())) { if (!instructorCourses.Contains(course.CourseID)) { instructorToUpdate.Courses.Add(course); } } 

If a course is not selected, but the course is in the Instructor.Courses navigation property, then the entry about it is deleted from the navigation property.

 else { if (instructorCourses.Contains(course.CourseID)) { instructorToUpdate.Courses.Remove(course); } } 

In Views \ Instructor \ Edit.cshtml, add the Courses field with the check boxes group immediately after the div divs for OfficeAssignment:

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

The code creates an HTML table of three columns, each of which contains a check box with the title of the course number and name. All check boxes have the same name (“selectedCourses”), which symbolizes their belonging to the same group. The value attribute of each check box is set to CourseID. When a page sends data, an array consisting of the selected check boxes and CourseID values ​​is passed to the controller.

When check boxes are generated, those that are already assigned to the teacher have a checked attribute.

After changing the course linkage to the teacher, you need to check the changes when you return to the Index page. To do this, add a column to the table on this page. For this, it is not necessary to use the ViewBag object, because the information that needs to be displayed is already in the Courses navigation property of the Instructor entity, which is passed to the view as a model.

In Views \ Instructor \ Index.cshtml, add the <th> Courses </ th> header cell immediately after <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> 

After that, add a new cell immediately after the cell with the office address:

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

Run the project and go to the Instructor Index page:

clip_image010

Click Edit on the teacher.

clip_image003[2]

Change the course mapping and click Save. Changes will be visible on the Index page.

Lesson introduction to work completed. You have completed simple CRUD operations, but have not dealt with concurrency issues. The next lesson will be devoted to the topic of parallelism, the issues of working with it.

Acknowledgments

Thanks for the help in the translation of Alexander Belotserkovsky.

Source: https://habr.com/ru/post/136155/


All Articles