Applications

Maintaining Related Data with Entity Framework

Maintaining Related Data with Entity Framework

Info from asp.net/mvc tutorial
Overview
Unfortunately out-of-the-box strongly typed MVC views do not expose relationship properties (navigation properties using the virtual keyword) for a given class. Needless to say, the problem here is the inability to relate entities. The solution, in a nut-shell is this: Provided you have configured your domain classes to have some sort of relation to one another, you’ll need to create view models to hold relationship info, create methods to populate the view models with relational information from the database, display them in the view, then finally attach values from the view to the entity.
Let’s use a Student/Course example to illustrate the mechanics.
        public class Student
        {
            public Student()
            {
                this.Course = new Collection<Course>(); *
            }
            public int StudentId { get; set; }
            // other student properties
            public virtual ICollection<Course> Course { get; set; } **
        }
 
        public class Course
        {
            public int CourseId { get; set; }
            public string CourseName { get; set; }
            // other course properties
            public virtual ICollection<Student> Student { get; set; }
        }
* I like to new-up any collections my domain classes might have in the constructor, this way I don’t forget down the road and get a NullReferenceException.
** I, for the life of me, cannot figure out why the pluralization matters here, but it does. Must be an EF convention or something. Make sure this property is singularized or you’ll get mysterious ModelState errors!
 
Create View Models
I want my create/edit pages to display the student’s details and a checkbox and label for each class available. Easy peasy, here’s what my View Model looks like:
        public class SelectedCourses
        {
            public string CourseName { get; set; }
            public bool Selected { get; set; }
        }
 
Method to populate View Model
The whole job of this method is to take all the available courses and previously selected ones, if any, and throw them into the ViewBag. Here’s what it looks like:
        private void PopulateCourses(Student student)
        {
            var courses = appService.GetAllCourses();
            var previouslySelectedCourses = new HashSet<int>(student.Courses.Select(c => c.CourseId));
            var viewModelCollection = new List<SelectedCourses>();
            foreach (var course in courses)
            {
                viewModel.Add(new SelectedCourses
                {
                    Name = course.CourseName,
                    Selected = previouslySelectedCourses.Contains(course.CourseId)
                });
            }
            ViewBag.Courses = viewModelCollection;
        }
You’ll be calling this method from the get & post controller actions right before you return the view.
 
Displaying Courses in the view (razor)
Next we’ll add the section of checkboxes which represent available courses, you may go about it however you wish, here’s what they did in the tutorial:
<div class ="editor-field">
            @Html.Label("Courses")
            @Html.ValidationMessage("CourseError")
            <table>
                <tr>
                @{
                    int i = 0;
                    List<App.ViewModelsFolder.SelectedCourses> courses = ViewBag.Courses;
                    foreach (var course in courses)
                    {
                        if (i++%3 == 0)
                        {
                            @: </tr> <tr>
                        }
                        @: <td>
                        <input type="checkbox" *
                               name ="courses" **
                               value ="@course.CourseName"
                               @(Html.Raw(course.Selected ? "checked=\"checked\"": ""))/>
                        @course.CourseName
                        @:</td>
                    }
                    @:</tr>
                }
            </table>
        </div>
* I didn’t have any luck with the Html checkbox helper so I just made them with mark-up
** In the http post method in the controller, we’ll take the FormCollection model as a parameter, if you name all these checkboxes the same, FormCollection will group them nicely. This is how we’ll map the data to the entity.
 
Map the values from the view to the entity then save it to the database
Here’s the http post method with a little validation mixed in, requiring that at least one course is chosen, check it:
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Student student, FormCollection form)
        {
            try
            {
                string[] courses = form.GetValues("courses");
                foreach (var course in courses)
                {
                    student.Course.Add(appService.GetCourseEntityFromString(course));
                }
            }
            catch (NullReferenceException)
            {
                ModelState.AddModelError("CourseError", "Must have at least one course selected");
            }
            if (ModelState.IsValid)
            {
                appService.CreateStudent(student);
                return RedirectToAction("Index");
            }
            PopulateCourses(student); *
            return View(student);
        }
* You’ll have to repopulate the courses if the ModelState wasn’t valid.
Also, notice that I’m only handling relational data, all the other Student properties are taken care of with the model binder.

Leave a Reply