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

Original author: ASP.NET Team
  • Transfer
This is an extension to the Entity Framework and ASP.NET MVC 3 development article series. You can find the first chapters at the following links:
In previous lessons, we displayed data. Now you will update them. For most relationships, you can update related data using foreign keys. For many-to-many relationships, EF does not use a directly joined table, so you must manually add and remove entities from the corresponding navigation properties.

The results are presented in the illustrations.

clip_image001clip_image002clip_image003

Editing Create and Edit Pages for Courses


When creating a new essence of the course, it should be connected with the existing faculty. To ensure this, the generated code includes a controller method and Create and Edit views with drop-down lists to highlight the faculty. The drop-down list defines the foreign key property of Course.DepartmentID, all that EF needs to load the Department navigation property with the corresponding Department entities. You will use the generated code with slight modifications 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 the view in the ViewBag property. The method accepts a parameter that allows the caller to optionally determine the default item.

The HttpGet create method calls the PopulateDepartmentsDropDownList method without specifying the selected item, 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 editable course:

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

The HttpPost methods 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 before the Title field to enter the course number by the user. It was previously explained that the properties of primary keys are not generated on the view, but in this case the primary key has a meaning, therefore it is necessary to give the user the opportunity to enter its value.

@Html.LabelFor(model => model.CourseID)
@Html.EditorFor(model => model.CourseID) @Html.ValidationMessageFor(model => model.CourseID)

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

@Html.LabelFor(model => model.CourseID)
@Html.DisplayFor(model => model.CourseID)

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 with 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 displays with updated course data.

Adding an Edit Page for Instructors


When you edit a record about a teacher, you can also update a record about his office. An Instructor entity is associated with OfficeAssignment as one-to-zero-or-to-one, which means that the following situations need to be handled:
  • If the user removes the binding to the office, the OfficeAssignment entity must be deleted.
  • If the user enters an incorrect value into the office property, it is necessary to create a new OfficeAssignment entity.
  • If the user changes the value of the office record, you must change the value of the corresponding OfficeAssignment entity.
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 a ViewBag and defines eager loading for the related OfficeAssignment and Course entities. (Courses will be needed later.) You cannot define eager loading for the Find method, so the Where and Single methods are used to select a teacher instead.

Replace the HttpPost Edit method code with the following code that processes the editing of the 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:
  • Returns an Instructor entity from a database using eager loading for OfficeAssignment and Courses navigation properties. We did the same for the HttpGet Edit method.
  • Updates the Instructor entity excluding Courses navigation property:
    If (TryUpdateModel (instructorToUpdate, "", null, new string [] {"Courses"}))
    If the verification fails, TryUpdateModel returns false, and execution returns to return View.
  • If the office entry is empty, the Instructor.OfficeAssignment property is set to null, so the OfficeAssignment table is deleted.

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

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

@Html.LabelFor(model => model.OfficeAssignment.Location)
@Html.EditorFor(model => model.OfficeAssignment.Location) @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)

Select the Instructors tab and click Edit on the teacher.

clip_image005

Change the value of Office Location and click Save .

clip_image006

A new address appears on the Index page, and you can see the entry in the table when you open the OfficeAssignment table in Server Explorer .

clip_image007

Return to the Edit page, clear Office Location and click Save . The Index page displays a blank address and Server Explorer displays that the record has been deleted.

clip_image008

On the Edit page, enter a new value in Office Locationand click Save . A new address value appears on the Index page , and Server Explorer displays a new entry.

clip_image009

Adding Course Assignments to Instructor Edit Page


Teachers can teach an unlimited number of courses. You will refresh the Instructor Edit page by adding the possibility of assigning to a course: The

clip_image003 [1]

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

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

To transfer data to a view to generate the check boxes group, 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 information for the generation of 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(instructor.Courses.Select(c => c.CourseID)); 
    var viewModel = new List(); 
    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 for the new method loads all Course entities 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 associated with a teacher, teacher-related courses 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 box or not. After that, the list is passed in the ViewBag property to the view.

Add the Save button handler code: 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(); 
        return; 
    } 
    var selectedCoursesHS = new HashSet(selectedCourses); 
    var instructorCourses = new HashSet 
        (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 UpdateInstructorCourses initializes the Courses navigation property with an empty collection:

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

The code iterates over all course records in the database, and if the check box for the course is checked, but there is no course 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 the course is not selected, but the course is in the Instructor.Courses navigation property, then the record 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 right after the div containers for OfficeAssignment:

@{ int cnt = 0; List courses = ViewBag.Courses; foreach (var course in courses) { if (cnt++ % 3 == 0) { @: } @: } @: }
@course.CourseID @:  @course.Title @:

The code creates an HTML table of three columns, each of which contains a check box with a heading from 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 the page sends data, an array consisting of selected check boxes and CourseID values ​​is passed to the controller.

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

After changing the course binding to the teacher, you need to check the changes when returning to the Index page. To do this, add a column to the table on this page. To do 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 Courses header cell immediately after Office:

  
      
    Last Name  
    First Name  
    Hire Date  
    Office 
    Courses 
 

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

 
    @{ 
        foreach (var course in item.Courses) 
        { 
            @course.CourseID @:  @course.Title 
} }

Run the project and go to the Instructor Index page :

clip_image010

Click Edit on the teacher.

clip_image003 [2]

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

The lesson-introduction to the work with the delivered is completed. You completed the simple CRUD operations, but did not deal with concurrency issues. The next lesson will be devoted to the topic of parallelism, issues of working with it.

Acknowledgments

Thank you for your help in translating Alexander Belotserkovsky.

Also popular now: