18 KiB
title | author | description | ms.author | ms.custom | ms.date | uid |
---|---|---|---|---|---|---|
Razor Pages with EF Core in ASP.NET Core - Read Related Data - 6 of 8 | rick-anderson | In this tutorial you read and display related data -- that is, data that the Entity Framework loads into navigation properties. | riande | mvc | 10/24/2018 | data/ef-rp/read-related-data |
Razor Pages with EF Core in ASP.NET Core - Read Related Data - 6 of 8
By Tom Dykstra, Jon P Smith, and Rick Anderson
[!INCLUDE about the series]
In this tutorial, related data is read and displayed. Related data is data that EF Core loads into navigation properties.
If you run into problems you can't solve, download or view the completed app. Download instructions.
The following illustrations show the completed pages for this tutorial:
Eager, explicit, and lazy Loading of related data
There are several ways that EF Core can load related data into the navigation properties of an entity:
-
Eager loading. Eager loading is when a query for one type of entity also loads related entities. When the entity is read, its related data is retrieved. This typically results in a single join query that retrieves all of the data that's needed. EF Core will issue multiple queries for some types of eager loading. Issuing multiple queries can be more efficient than was the case for some queries in EF6 where there was a single query. Eager loading is specified with the
Include
andThenInclude
methods.Eager loading sends multiple queries when a collection navigation is included:
- One query for the main query
- One query for each collection "edge" in the load tree.
-
Separate queries with
Load
: The data can be retrieved in separate queries, and EF Core "fixes up" the navigation properties. "fixes up" means that EF Core automatically populates the navigation properties. Separate queries withLoad
is more like explict loading than eager loading.Note: EF Core automatically fixes up navigation properties to any other entities that were previously loaded into the context instance. Even if the data for a navigation property is not explicitly included, the property may still be populated if some or all of the related entities were previously loaded.
-
Explicit loading. When the entity is first read, related data isn't retrieved. Code must be written to retrieve the related data when it's needed. Explicit loading with separate queries results in multiple queries sent to the DB. With explicit loading, the code specifies the navigation properties to be loaded. Use the
Load
method to do explicit loading. For example: -
Lazy loading. Lazy loading was added to EF Core in version 2.1. When the entity is first read, related data isn't retrieved. The first time a navigation property is accessed, the data required for that navigation property is automatically retrieved. A query is sent to the DB each time a navigation property is accessed for the first time.
-
The
Select
operator loads only the related data needed.
Create a Course page that displays department name
The Course entity includes a navigation property that contains the Department
entity. The Department
entity contains the department that the course is assigned to.
To display the name of the assigned department in a list of courses:
- Get the
Name
property from theDepartment
entity. - The
Department
entity comes from theCourse.Department
navigation property.
Scaffold the Course model
Visual Studio
Follow the instructions in Scaffold the student model and use Course
for the model class.
.NET Core CLI
Run the following command:
dotnet aspnet-codegenerator razorpage -m Course -dc SchoolContext -udl -outDir Pages\Courses --referenceScriptLibraries
The preceding command scaffolds the Course
model. Open the project in Visual Studio.
Open Pages/Courses/Index.cshtml.cs and examine the OnGetAsync
method. The scaffolding engine specified eager loading for the Department
navigation property. The Include
method specifies eager loading.
Run the app and select the Courses link. The department column displays the DepartmentID
, which isn't useful.
Update the OnGetAsync
method with the following code:
The preceding code adds AsNoTracking
. AsNoTracking
improves performance because the entities returned are not tracked. The entities are not tracked because they're not updated in the current context.
Update Pages/Courses/Index.cshtml with the following highlighted markup:
The following changes have been made to the scaffolded code:
-
Changed the heading from Index to Courses.
-
Added a Number column that shows the
CourseID
property value. By default, primary keys aren't scaffolded because normally they're meaningless to end users. However, in this case the primary key is meaningful. -
Changed the Department column to display the department name. The code displays the
Name
property of theDepartment
entity that's loaded into theDepartment
navigation property:@Html.DisplayFor(modelItem => item.Department.Name)
Run the app and select the Courses tab to see the list with department names.
Loading related data with Select
The OnGetAsync
method loads related data with the Include
method:
The Select
operator loads only the related data needed. For single items, like the Department.Name
it uses a SQL INNER JOIN. For collections, it uses another database access, but so does the Include
operator on collections.
The following code loads related data with the Select
method:
The CourseViewModel
:
See IndexSelect.cshtml and IndexSelect.cshtml.cs for a complete example.
Create an Instructors page that shows Courses and Enrollments
In this section, the Instructors page is created.
This page reads and displays related data in the following ways:
- The list of instructors displays related data from the
OfficeAssignment
entity (Office in the preceding image). TheInstructor
andOfficeAssignment
entities are in a one-to-zero-or-one relationship. Eager loading is used for theOfficeAssignment
entities. Eager loading is typically more efficient when the related data needs to be displayed. In this case, office assignments for the instructors are displayed. - When the user selects an instructor (Harui in the preceding image), related
Course
entities are displayed. TheInstructor
andCourse
entities are in a many-to-many relationship. Eager loading is used for theCourse
entities and their relatedDepartment
entities. In this case, separate queries might be more efficient because only courses for the selected instructor are needed. This example shows how to use eager loading for navigation properties in entities that are in navigation properties. - When the user selects a course (Chemistry in the preceding image), related data from the
Enrollments
entity is displayed. In the preceding image, student name and grade are displayed. TheCourse
andEnrollment
entities are in a one-to-many relationship.
Create a view model for the Instructor Index view
The instructors page shows data from three different tables. A view model is created that includes the three entities representing the three tables.
In the SchoolViewModels folder, create InstructorIndexData.cs with the following code:
Scaffold the Instructor model
Visual Studio
Follow the instructions in Scaffold the student model and use Instructor
for the model class.
.NET Core CLI
Run the following command:
dotnet aspnet-codegenerator razorpage -m Instructor -dc SchoolContext -udl -outDir Pages\Instructors --referenceScriptLibraries
The preceding command scaffolds the Instructor
model.
Run the app and navigate to the instructors page.
Replace Pages/Instructors/Index.cshtml.cs with the following code:
The OnGetAsync
method accepts optional route data for the ID of the selected instructor.
Examine the query in the Pages/Instructors/Index.cshtml.cs file:
The query has two includes:
OfficeAssignment
: Displayed in the instructors view.CourseAssignments
: Which brings in the courses taught.
Update the instructors Index page
Update Pages/Instructors/Index.cshtml with the following markup:
The preceding markup makes the following changes:
-
Updates the
page
directive from@page
to@page "{id:int?}"
."{id:int?}"
is a route template. The route template changes integer query strings in the URL to route data. For example, clicking on the Select link for an instructor with only the@page
directive produces a URL like the following:http://localhost:1234/Instructors?id=2
When the page directive is
@page "{id:int?}"
, the previous URL is:http://localhost:1234/Instructors/2
-
Page title is Instructors.
-
Added an Office column that displays
item.OfficeAssignment.Location
only ifitem.OfficeAssignment
isn't null. Because this is a one-to-zero-or-one relationship, there might not be a related OfficeAssignment entity.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
-
Added a Courses column that displays courses taught by each instructor. See Explicit Line Transition with
@:
for more about this razor syntax. -
Added code that dynamically adds
class="success"
to thetr
element of the selected instructor. This sets a background color for the selected row using a Bootstrap class.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "success"; } <tr class="@selectedRow">
-
Added a new hyperlink labeled Select. This link sends the selected instructor's ID to the
Index
method and sets a background color.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Run the app and select the Instructors tab. The page displays the Location
(office) from the related OfficeAssignment
entity. If OfficeAssignment` is null, an empty table cell is displayed.
Click on the Select link. The row style changes.
Add courses taught by selected instructor
Update the OnGetAsync
method in Pages/Instructors/Index.cshtml.cs with the following code:
Add public int CourseID { get; set; }
Examine the updated query:
The preceding query adds the Department
entities.
The following code executes when an instructor is selected (id != null
). The selected instructor is retrieved from the list of instructors in the view model. The view model's Courses
property is loaded with the Course
entities from that instructor's CourseAssignments
navigation property.
The Where
method returns a collection. In the preceding Where
method, only a single Instructor
entity is returned. The Single
method converts the collection into a single Instructor
entity. The Instructor
entity provides access to the CourseAssignments
property. CourseAssignments
provides access to the related Course
entities.
The Single
method is used on a collection when the collection has only one item. The Single
method throws an exception if the collection is empty or if there's more than one item. An alternative is SingleOrDefault
, which returns a default value (null in this case) if the collection is empty. Using SingleOrDefault
on an empty collection:
- Results in an exception (from trying to find a
Courses
property on a null reference). - The exception message would less clearly indicate the cause of the problem.
The following code populates the view model's Enrollments
property when a course is selected:
Add the following markup to the end of the Pages/Instructors/Index.cshtml Razor Page:
The preceding markup displays a list of courses related to an instructor when an instructor is selected.
Test the app. Click on a Select link on the instructors page.
Show student data
In this section, the app is updated to show the student data for a selected course.
Update the query in the OnGetAsync
method in Pages/Instructors/Index.cshtml.cs with the following code:
Update Pages/Instructors/Index.cshtml. Add the following markup to the end of the file:
The preceding markup displays a list of the students who are enrolled in the selected course.
Refresh the page and select an instructor. Select a course to see the list of enrolled students and their grades.
Using Single
The Single
method can pass in the Where
condition instead of calling the Where
method separately:
The preceding Single
approach provides no benefits over using Where
. Some developers prefer the Single
approach style.
Explicit loading
The current code specifies eager loading for Enrollments
and Students
:
Suppose users rarely want to see enrollments in a course. In that case, an optimization would be to only load the enrollment data if it's requested. In this section, the OnGetAsync
is updated to use explicit loading of Enrollments
and Students
.
Update the OnGetAsync
with the following code:
The preceding code drops the ThenInclude method calls for enrollment and student data. If a course is selected, the highlighted code retrieves:
- The
Enrollment
entities for the selected course. - The
Student
entities for eachEnrollment
.
Notice the preceding code comments out .AsNoTracking()
. Navigation properties can only be explicitly loaded for tracked entities.
Test the app. From a users perspective, the app behaves identically to the previous version.
The next tutorial shows how to update related data.