Reading Related Data with the Entity Framework in an ASP.NET MVC Application (5 of 10)
The Contoso University sample web application demonstrates how to create ASP.NET MVC applications using the Entity Framework. The sample application is a website for a fictional Contoso University. It includes functionality such as student admission, course creation, and instructor assignments.
This tutorial series explains the steps taken to build the Contoso University sample application. You can download the completed application or create it by following the steps in the tutorial. The tutorial shows examples in C#. The downloadable sample contains code in both C# and Visual Basic. If you have questions that are not directly related to the tutorial, you can post them to the ASP.NET Entity Framework forum or the Entity Framework and LINQ to Entities forum.
This tutorial series assumes you know how to work with ASP.NET MVC in Visual Studio. If you don’t, a good place to start is a basic ASP.NET MVC Tutorial. If you prefer to work with the ASP.NET Web Forms model, see the Getting Started with the Entity Framework and Continuing with the Entity Framework tutorials.
Before you start, make sure you have the following software installed on your computer:
- Visual Studio 2010 SP1 or Visual Web Developer Express 2010 SP1 (If you use one of these links, the following items will be installed automatically.)
- ASP.NET MVC 3 Tools Update
- Microsoft SQL Server Compact 4.0
- Microsoft Visual Studio 2010 SP1 Tools for SQL Server Compact 4.0
In the previous tutorial you completed the School data model. In this tutorial you'll read and display related data — that is, data that the Entity Framework loads into navigation properties.
The following illustrations show the pages that you'll work with.
Lazy, Eager, and Explicit Loading of Related Data
There are several ways that the Entity Framework can load related data into the navigation properties of an entity:
- Lazy loading. When the entity is first read, related data isn't retrieved. However, the first time you attempt to access a navigation property, the data required for that navigation property is automatically retrieved. This results in multiple queries sent to the database — one for the entity itself and one each time that related data for the entity must be retrieved.
-
Eager loading. When the entity is read, related data is retrieved along with it. This typically results in a single join query that retrieves all of the data that's needed. You specify eager loading by using the
Include
method. -
Explicit loading. This is similar to lazy loading, except that you explicitly retrieve the related data in code; it doesn't happen automatically when you access a navigation property. You load related data manually by getting the object state manager entry for an entity and calling the
Collection.Load
method for collections or theReference.Load
method for properties that hold a single entity. (In the following example, if you wanted to load the Administrator navigation property, you'd replaceCollection(x => x.Courses)
withReference(x => x.Administrator)
.)
Because they don't immediately retrieve the property values, lazy loading and explicit loading are also both known as deferred loading.
In general, if you know you need related data for every entity retrieved, eager loading offers the best performance, because a single query sent to the database is typically more efficient than separate queries for each entity retrieved. For example, in the above examples, suppose that each department has ten related courses. The eager loading example would result in just a single (join) query. The lazy loading and explicit loading examples would both result in eleven queries.
On the other hand, if you need to access an entity's navigation properties only infrequently or only for a small portion of a set of entities you're processing, lazy loading may be more efficient, because eager loading would retrieve more data than you need. Typically you'd use explicit loading only when you've turned lazy loading off. One scenario when you might turn lazy loading off is during serialization, when you know you don't need all navigation properties loaded. If lazy loading were on, all navigation properties would all be loaded automatically, because serialization accesses all properties.
The database context class performs lazy loading by default. There are two ways to turn off lazy loading:
- For specific navigation properties, omit the
virtual
keyword when you declare the property. - For all navigation properties, set
LazyLoadingEnabled
tofalse
.
Lazy loading can mask code that causes performance problems. For example, code that doesn't specify eager or explicit loading but processes a high volume of entities and uses several navigation properties in each iteration might be very inefficient (because of many round trips to the database), but it would work without errors if it relies on lazy loading. Temporarily disabling lazy loading is one way to discover where the code is relying on lazy loading, because without it the navigation properties will be null and the code will fail.
Creating a Courses Index Page That Displays Department Name
The Course
entity includes a navigation property that contains the Department
entity of the department that the course is assigned to. To display the name of the assigned department in a list of courses, you need to get the Name
property from the Department
entity that is in the Course.Department
navigation property.
Create a controller for the Course
entity type, using the same options that you did earlier for the Student
controller, as shown in the following illustration:
Open Controllers\CourseController.cs and look at the Index
method:
public ViewResult Index() { var courses = db.Courses.Include(c => c.Department); return View(courses.ToList()); }
The automatic scaffolding has specified eager loading for the Department
navigation property by using the Include
method.
Open Views\Course\Index.cshtml and replace the existing code with the following code:
@model IEnumerable<ContosoUniversity.Models.Course> @{ ViewBag.Title = "Courses"; } <h2>Courses</h2> <p> @Html.ActionLink("Create New", "Create") </p> <table> <tr> <th></th> <th>Number</th> <th>Title</th> <th>Credits</th> <th>Department</th> </tr> @foreach (var item in Model) { <tr> <td> @Html.ActionLink("Edit", "Edit", new { id=item.CourseID }) | @Html.ActionLink("Details", "Details", new { id=item.CourseID }) | @Html.ActionLink("Delete", "Delete", new { id=item.CourseID }) </td> <td> @Html.DisplayFor(modelItem => item.CourseID) </td> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @Html.DisplayFor(modelItem => item.Credits) </td> <td> @Html.DisplayFor(modelItem => item.Department.Name) </td> </tr> } </table>
You've made the following changes to the scaffolded code:
- Changed the heading from Index to Courses.
- Moved the row links to the left.
- Added a column under the heading Number that shows the
CourseID
property value. (Primary keys aren't scaffolded because normally they are meaningless. However, in this case the primary key is meaningful and you want to show it.) - Changed the last column heading from DepartmentID (the name of the foreign key to the
Department
entity) to Department.
Notice that for the last column, the scaffolded code displays the Name
property of the Department
entity that's loaded into the Department
navigation property:
<td> @Html.DisplayFor(modelItem => item.Department.Name) </td>
Run the page (select the Courses tab on the Contoso University home page) to see the list with department names.
Creating an Instructors Index Page That Shows Courses and Enrollments
In this section you'll create a controller and view for the Instructor
entity in order to display the Instructors Index page:
This page reads and displays related data in the following ways:
- The list of instructors displays related data from the
OfficeAssignment
entity. TheInstructor
andOfficeAssignment
entities are in a one-to-zero-or-one relationship. You'll use eager loading for theOfficeAssignment
entities. As explained earlier, eager loading is typically more efficient when you need the related data for all retrieved rows of the primary table. In this case, you want to display office assignments for all displayed instructors. - When the user selects an instructor, related
Course
entities are displayed. TheInstructor
andCourse
entities are in a many-to-many relationship. You will use eager loading for theCourse
entities and their relatedDepartment
entities. In this case, lazy loading might be more efficient because you need courses only for the selected instructor. However, this example shows how to use eager loading for navigation properties within entities that are themselves in navigation properties. - When the user selects a course, related data from the
Enrollments
entity set is displayed. TheCourse
andEnrollment
entities are in a one-to-many relationship. You'll add explicit loading forEnrollment
entities and their relatedStudent
entities. (Explicit loading isn't necessary because lazy loading is enabled, but this shows how to do explicit loading.)
Creating a View Model for the Instructor Index View
The Instructor Index page shows three different tables. Therefore, you'll create a view model that includes three properties, each holding the data for one of the tables.
In the ViewModels folder, create InstructorIndexData.cs and replace the existing code with the following code:
using System; using System.Collections.Generic; using ContosoUniversity.Models; namespace ContosoUniversity.ViewModels { public class InstructorIndexData { public IEnumerable<Instructor> Instructors { get; set; } public IEnumerable<Course> Courses { get; set; } public IEnumerable<Enrollment> Enrollments { get; set; } } }
Adding a Style for Selected Rows
To mark selected rows you need a different background color. To provide a style for this UI, add the following code to the section marked MISC
in Content\Site.css, as shown in the following example:
/* MISC ----------------------------------------------------------*/ .selectedrow { background-color: #EEEEEE; }
Creating the Instructor Controller and Views
Create a controller for the Instructor
entity type, using the same options that you did earlier for the Student
controller, as shown in the following illustration:
Open Controllers\InstructorController.cs and add a using
statement for the ViewModels
namespace:
using ContosoUniversity.ViewModels;
The scaffolded code in the Index
method specifies eager loading only for the OfficeAssignment
navigation property:
public ViewResult Index() { var instructors = db.Instructors.Include(i => i.OfficeAssignment); return View(instructors.ToList()); }
Replace the Index
method with the following code to load additional related data and put it in the view model:
public ActionResult Index(Int32? id, Int32? courseID) { var viewModel = new InstructorIndexData(); viewModel.Instructors = db.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses.Select(c => c.Department)) .OrderBy(i => i.LastName); if (id != null) { ViewBag.InstructorID = id.Value; viewModel.Courses = viewModel.Instructors.Where(i => i.InstructorID == id.Value).Single().Courses; } if (courseID != null) { ViewBag.CourseID = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where(x => x.CourseID == courseID).Single().Enrollments; } return View(viewModel); }
The method accepts optional query string parameters that provide the ID values of the selected instructor and selected course, and passes all of the required data to the view. The query string parameters are provided by the Select hyperlinks on the page.
The code begins by creating an instance of the view model and putting in it the list of instructors:
var viewModel = new InstructorIndexData(); viewModel.Instructors = db.Instructors .Include(i => i.OfficeAssignment); .Include(i => i.Courses.Select(c => c.Department)) .OrderBy(i => i.LastName);
This statement specifies eager loading for the Instructor.OfficeAssignment
and the Instructor.Courses
navigation property. For the related Course
entities, eager loading is specified for the Course.Department
navigation property by using the Select
method within the Include
method. The results are sorted by last name.
If an instructor was selected, the selected instructor is retrieved from the list of instructors in the view model. The view model's Courses
property is then loaded with the Course
entities from that instructor's Courses
navigation property.
if (id != null) { ViewBag.InstructorID = id.Value; viewModel.Courses = viewModel.Instructors.Where(i => i.InstructorID == id.Value).Single().Courses; }
The Where
method returns a collection, but in this case the criteria passed to that method result in only a single Instructor
entity being returned. The Single
method converts the collection into a single Instructor
entity, which gives you access to that entity's Courses
property.
You use the Single
method on a collection when you know the collection will have only one item. The Single
method throws an exception if the collection passed to it is empty or if there's more than one item. An alternative is SingleOrDefault
, which returns null if the collection is empty. However, in this case that would still result in an exception (from trying to find a Courses
property on a null reference), and the exception message would less clearly indicate the cause of the problem. When you call the Single
method, you can also pass in the Where
condition instead of calling the Where
method separately:
.Single(i => i.InstructorID == id.Value)
Instead of:
.Where(I => i.InstructorID == id.Value).Single()
Next, if a course was selected, the selected course is retrieved from the list of courses in the view model. Then the view model's Enrollments
property is loaded with the Enrollment
entities from that course's Enrollments
navigation property.
if (courseID != null) { ViewBag.CourseID = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where(x => x.CourseID == courseID).Single().Enrollments; }
Finally, the view model is returned to the view:
return View(viewModel);
Modifying the Instructor Index View
In Views\Instructor\Index.cshtml, replace the existing code with the following code:
@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> </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> </tr> } </table>
You've made the following changes to the existing code:
- Changed the page title from Index to Instructors.
- Moved the row link columns to the left.
- Removed the FullName column.
- Added an Office column that displays
item.OfficeAssignment.Location
only ifitem.OfficeAssignment
is not null. (Because this is a one-to-zero-or-one relationship, there might not be a relatedOfficeAssignment
entity.)<td> @if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location } </td>
- Added code that will dynamically add
class="selectedrow"
to thetr
element of the selected instructor. This sets a background color for the selected row using the CSS class that you created earlier. (Thevalign
attribute will be useful in the following tutorial when you add a multirow column to the table.)string selectedRow = ""; if (item.InstructorID == ViewBag.InstructorID) { selectedRow = "selectedrow"; } <tr class="@selectedRow" valign="top">
- Added a new
ActionLink
labeled Select immediately before the other links in each row, which causes the selected instructor ID to be sent to theIndex
method.
Run the page to see the list of instructors. The page displays the Location
property of related OfficeAssignment
entities and an empty table cell when there's no related OfficeAssignment
entity.
While you still have Views\Instructor\Index.cshtml open, after the table
element, add the following code. This displays a list of courses related to an instructor when an instructor is selected.
@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> }
This code reads the Courses
property of the view model to display a list of courses. It also provides a Select
hyperlink that sends the ID of the selected course to the Index
action method.
Run the page and select an instructor. Now you see a grid that displays courses assigned to the selected instructor, and for each course you see the name of the assigned department.
Note If the selected row isn't highlighted, click the Refresh button on your browser (or press F5) ; this is sometimes required in order to reload the .css file. If that doesn't work, you might have to do a hard refresh (hold down the CTRL key while clicking the Refresh button, or press CTRL-F5).
After the code block you just added, add the following code. This displays a list of the students who are enrolled in a course when that course is selected.
@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> }
This code reads the Enrollments
property of the view model in order to display a list of students enrolled in the course. The DisplayFor
helper is used so that null grades will display as "No grade", as specified in the DisplayFormat
data annotation attribute for that field.
Run the page and select an instructor. Then select a course to see the list of enrolled students and their grades.
Adding Explicit Loading
Open InstructorController.cs and look at how the Index
method gets the list of enrollments for a selected course:
if (courseID != null) { ViewBag.CourseID = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where(x => x.CourseID == courseID).Single().Enrollments; }
When you retrieved the list of instructors, you specified eager loading for the Courses
navigation property and for the Department
property of each course. Then you put the Courses
collection in the view model, and now you're accessing the Enrollments
navigation property from one entity in that collection. Because you didn't specify eager loading for the Course.Enrollments
navigation property, the data from that property is appearing in the page as a result of lazy loading.
If you disabled lazy loading without changing the code in any other way, the Enrollments
property would be null regardless of how many enrollments the course actually had. In that case, to load the Enrollments
property, you'd have to specify either eager loading or explicit loading. You've already seen how to do eager loading. In order to see an example of explicit loading, replace the Index
method with the following code, which explicitly loads the Enrollments
property:
public ActionResult Index(Int32? id, Int32? courseID) { var viewModel = new InstructorIndexData(); viewModel.Instructors = db.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses.Select(c => c.Department)) .OrderBy(i => i.LastName); if (id != null) { ViewBag.InstructorID = id.Value; viewModel.Courses = viewModel.Instructors.Where(i => i.InstructorID == id.Value).Single().Courses; } if (courseID != null) { ViewBag.CourseID = courseID.Value; var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single(); db.Entry(selectedCourse).Collection(x => x.Enrollments).Load(); foreach (Enrollment enrollment in selectedCourse.Enrollments) { db.Entry(enrollment).Reference(x => x.Student).Load(); } viewModel.Enrollments = selectedCourse.Enrollments; } return View(viewModel); }
After getting the selected Course
entity, the new code explicitly loads that course's Enrollments
navigation property:
db.Entry(selectedCourse).Collection(x => x.Enrollments).Load();
Then it explicitly loads each Enrollment
entity's related Student
entity:
db.Entry(enrollment).Reference(x => x.Student).Load();
Notice that you use the Collection
method to load a collection property, but for a property that holds just one entity, you use the Reference
method. You can run the Instructor Index page now and you'll see no difference in what's displayed on the page, although you've changed how the data is retrieved.
You've now used all three ways (lazy, eager, and explicit) to load related data into navigation properties. In the next tutorial you'll learn how to update related data.
Links to other Entity Framework resources, can be found at the end of the last tutorial in this series.
Comments (0) RSS Feed