The Contoso University sample web application demonstrates how to create ASP.NET Core 1.0 MVC web applications using Entity Framework Core 1.0 and Visual Studio 2015. For information about the tutorial series, see the first tutorial in the series.html
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.ios
在前面的教程中,已經完成了關於School的數據模型。在本教程中,將實現對關係數據的讀取和顯示,關係數據是的就是經過EF加載的導航屬性數據。web
The following illustrations show the pages that you’ll work with.數據庫
Sections:c#
Explicit loading. When the entity is first read, related data isn’t retrieved. You write code that retrieves the related data if it’s needed. As in the case of eager loading with separate queries, explicit loading results in multiple queries sent to the database. The difference is that with explicit loading, the code specifies the navigation properties to be loaded. Entity Framework Core 1.0 does not provide an explicit loading API.
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. A query is sent to the database each time you try to get data from a navigation property for the first time. Entity Framework Core 1.0 does not support lazy loading.
If you know you need related data for every entity retrieved, eager loading often 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, suppose that each department has ten related courses. Eager loading of all related data would result in just a single (join) query and a single round trip to the database. A separate query for courses for each department would result in eleven round trips to the database. The extra round trips to the database are especially detrimental to performance when latency is high.
若是你知道每個取回的實體都須要關係數據,那麼預加載一般提供了最好的性能,由於發到數據庫的單獨查詢一般狀況下比分步查詢更有效率。例如,假定每一個繫有10個關係課程,預加載全部的關係數據會致使僅有一個單獨的(join)查詢,這樣就會在數據庫中進行一次循環。對每一個系的課程執行分步查詢將致使數據庫中11次循環。當潛在因素較高時,數據庫額外的循環特別影響性能。
On the other hand, in some scenarios separate queries is more efficient. Eager loading of all related data in one query might cause a very complex join to be generated, which SQL Server can’t process efficiently. Or if you need to access an entity’s navigation properties only for a subset of a set of the entities you’re processing, separate queries might perform better because eager loading of everything up front would retrieve more data than you need. If performance is critical, it’s best to test performance both ways in order to make the best choice.
另外一方面,在某些場景下分步查詢會更有效率。在一條查詢中預加載全部的關係數據可能致使產生很是複雜的join語句,這使得SQL Server不能高效地執行。或者,若是僅須要使用集合的一個子集的實體導航實行,分步查詢可能執行起來效率更高,由於預加載全部的數據會返回比你須要的更多的數據。若是查詢的性能很是重要,最好測試這兩種方法以便作出正確的選擇。
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.
設計時,課程實體包含了系實體的導航屬性。要在課程列表中顯示指定的系名稱,須要取得Course.Department導航屬性鏈接的系實體相關的Name屬性。
Create a controller named CoursesController for the Course entity type, using the same options for the MVC Controller with views, using Entity Framework scaffolder that you did earlier for the Students controller, as shown in the following illustration:
爲課程實體類型新建一個名爲CoursesController的控制器,使用MVC Controller with views, 使用EF基架生成器,以下圖所示:
Open CourseController.cs and examine the Index
method. The automatic scaffolding has specified eager loading for the Department
navigation property by using the Include
method.
Replace the Index
method with the following code that uses a more appropriate name for the IQueryable
that returns Course entities (courses
instead of schoolContext
):
打開CourseController.cs文件並對Index方法進行測試。自動搭建的結構爲Department導航屬性指定了預加載模式(使用Include方法)。用下列代碼替換Index方法,使用更恰當的IQueryable返回課程實體(使用courses而不是schoolContext):
public async Task<IActionResult> Index() { var courses = _context.Courses .Include(c => c.Department) .AsNoTracking(); return View(await courses.ToListAsync()); }
Open Views/Courses/Index.cshtml and replace the template code with the following code. The changes are highlighted:
@model IEnumerable<ContosoUniversity.Models.Course> @{ ViewData["Title"] = "Courses"; } <h2>Courses</h2> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.CourseID) </th> <th> @Html.DisplayNameFor(model => model.Credits) </th> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.Department) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.CourseID) </td> <td> @Html.DisplayFor(modelItem => item.Credits) </td> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @Html.DisplayFor(modelItem => item.Department.Name) </td> <td> <a asp-action="Edit" asp-route-id="@item.CourseID">Edit</a> | <a asp-action="Details" asp-route-id="@item.CourseID">Details</a> | <a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a> </td> </tr> } </tbody> </table>
You’ve made the following changes 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 are meaningless to end users. However, in this case the primary key is meaningful and you want to show it.
Added the Department column. Notice that for the Department column, the code displays the Name
property of the Department entity that’s loaded into the Department
navigation property:
添加了Department列。注意Department列,顯示系實體的Name屬性的代碼,它加載到Department導航屬性中:
@Html.DisplayFor(modelItem => item.Department.Name)
Run the page (select the Courses tab on the Contoso University home page) to see the list with department names.
運行該頁面(在Home頁面上選擇Courses)查看帶有系名的列表。
In this section you’ll create a controller and view for the Instructor entity in order to display the Instructors page:
在該節中,你將新建Instructor實體的控制器和視圖,用來顯示Instrctors頁面:
This page reads and displays related data in the following ways:
該頁面經過如下方法讀取、顯示關係數據:
The Instructors page shows data from 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 SchoolViewModels folder, create InstructorIndexData.cs and replace the existing code with the following code:
講師頁面顯示的數據來自於三個不一樣的表。所以,你將新建一個視圖模型,其包含三個屬性,每一個屬性控制一個表的數據。
在SchoolViewModels文件夾中,新建一個文件InstructorIndexData.cs,並用下列代碼替換原有代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace ContosoUniversity.Models.SchoolViewModels { public class InstructorIndexData { public IEnumerable<Instructor> Instructors { get; set; } public IEnumerable<Course> Courses { get; set; } public IEnumerable<Enrollment> Enrollments { get; set; } } }
Create an Instructors controller with EF read/write actions as shown in the following illustration:
用EF read/write動做新建講師控制器,以下圖所示:
Open InstructorsController.cs and add a using statement for the ViewModels namespace:
打開InstructorsController.cs,而且添加using語句:
using ContosoUniversity.Models.SchoolViewModels;
Replace the Index method with the following code to do eager loading of related data and put it in the view model.
用下列代碼替換Index方法,以實現關係數據的預加載,並將其放置於視圖模型內。
public async Task<IActionResult> Index(int? id, int? courseID) { var viewModel = new InstructorIndexData(); viewModel.Instructors = await _context.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Enrollments) .ThenInclude(i => i.Student) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Department) .AsNoTracking() .OrderBy(i => i.LastName) .ToListAsync(); if (id != null) { ViewData["InstructorID"] = id.Value; Instructor instructor = viewModel.Instructors.Where( i => i.ID == id.Value).Single(); viewModel.Courses = instructor.Courses.Select(s => s.Course); } if (courseID != null) { ViewData["CourseID"] = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where( x => x.CourseID == courseID).Single().Enrollments; } return View(viewModel); }
The method accepts optional route data (id
) and a query string parameter (courseID
) that provide the ID values of the selected instructor and selected course. The parameters are provided by the Select hyperlinks on the page.
該方法接收了可選的路由數據(id)以及一個查詢字符串參數(courseID),該字符串提供了被選擇講師和課程的ID值。該參數經過頁面上的Select連接提供。
The code begins by creating an instance of the view model and putting in it the list of instructors. The code specifies eager loading for the Instructor.OfficeAssignment
and the Instructor.Courses
navigation property. Within the Courses
property, the Enrollments
and Department
properties are loaded, and within each Enrollment
entity the Student
property is loaded.
代碼開始新建了視圖模型實例,並將一個講師列表放入其中。代碼指定預加載Instructor.OfficeAssignment
和Instructor.Courses 導航屬性。在Courses 屬性中,加載了Enrollments
和Department屬性,同時每一個
Enrollment實體中的
Student屬性也進行了加載。
viewModel.Instructors = await _context.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Enrollments) .ThenInclude(i => i.Student) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Department) .AsNoTracking() .OrderBy(i => i.LastName) .ToListAsync();
Since the view always requires the OfficeAssignment entity, it’s more efficient to fetch that in the same query. Course entities are required when an instructor is selected in the web page, so a single query is better than multiple queries only if the page is displayed more often with a course selected than without.
由於視圖老是須要OfficeAssignment實體,因此在同一個查詢中獲取這些信息更爲高效。當在頁面中選擇某個講師時就會須要課程實體,因此只有在頁面常常顯示一個被選擇的課程時,使用單獨的查詢比複合查詢更好。
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.
若是選擇了一個講師,就會從視圖模型的講師列表中返回該講師。視圖模型中的Course屬性接下來就經過講師實體的Course導航屬性加載了。
if (id != null) { ViewData["InstructorID"] = id.Value; Instructor instructor = viewModel.Instructors.Where( i => i.ID == id.Value).Single(); viewModel.Courses = instructor.Courses.Select(s => s.Course); }
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. The Courses
property contains CourseInstructor
entities, from which you want only the related Course entities.
Where方法返回了一個集合,但這裏的規則是:傳遞給該方法的結果只須要一個單個的講師。Single方法將集合轉化爲一個單獨的講師實體,該實體可以使你鏈接那個實體的Courses屬性。Courses屬性包含了CourseInstructor實體,從該處你只想獲得Course實體的關係數據。
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 a default value (null in this case) 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方法。若是集合傳遞給它的是空值,或者有超過一條以上的記錄,Single方法就會拋出一個異常。一個相關的方法是SingleOrDefault,若是集合是空集,該方法返回一個默認值(在這裏是null)。然而,在這裏仍然會致使一個異常(來自試圖從一個null引用上尋找一個Courses實體),而且異常信息反應的問題緣由不太清楚。當你調用Single方法時,你也可傳遞一個Where條件,而不是單獨地調用Where方法:
.Single(i => i.ID == id.Value)
Instead of: 替代:
.Where(I => i.ID == 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.
接下來,若是選擇了一個課程,就會從視圖模型中的課程列表中返回該被選課程。而後,就加載了視圖模型中的Enrollments屬性,同時也就從該課程的Enrollments導航屬性加載了註冊實體。
if (courseID != null) { ViewData["CourseID"] = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where( x => x.CourseID == courseID).Single().Enrollments; }
In Views/Instructor/Index.cshtml, replace the template code with the following code. The changes (other than column reordering)are highlighted.注意高亮的修改部分:
@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData @{ ViewData["Title"] = "Instructors"; } <h2>Instructors</h2> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th>Last Name</th> <th>First Name</th> <th>Hire Date</th> <th>Office</th> <th>Courses</th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model.Instructors) { string selectedRow = ""; if (item.ID == (int?)ViewData["InstructorID"]) { selectedRow = "success"; } <tr class="@selectedRow"> <td> @Html.DisplayFor(modelItem => item.LastName) </td> <td> @Html.DisplayFor(modelItem => item.FirstMidName) </td> <td> @Html.DisplayFor(modelItem => item.HireDate) </td> <td> @if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location } </td> <td> @{ foreach (var course in item.Courses) { @course.Course.CourseID @: @course.Course.Title <br /> } } </td> <td> <a asp-action="Index" asp-route-id="@item.ID">Select</a> | <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> | <a asp-action="Details" asp-route-id="@item.ID">Details</a> | <a asp-action="Delete" asp-route-id="@item.ID">Delete</a> </td> </tr> } </tbody> </table>
You’ve made the following changes to the existing code: 已將代碼作了以下變化:
Changed the model class to InstructorIndexData
. 將模型類變動爲InstructorIndexData。
Changed the page title from Index to Instructors. 將頁首標題從Index變動爲Instructors。
Added an Office column that displays item.OfficeAssignment.Location
only if item.OfficeAssignment
is not null. (Because this is a one-to-zero-or-one relationship, there might not be a related OfficeAssignment entity.) 增長了Office列,若是item.OfficeAssignment 非空的狀況下,會顯示item.OfficeAssignment.Location ,(由於是1-0或-1的關係,有可能不是一個關係的OfficeAssignment實體)
@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
Added a Courses column that displays courses taught by each instructor. 增長了Course列,用於顯示每一個講師講授的課程。
Added code that dynamically adds class="success"
to the tr
element of the selected instructor. This sets a background color for the selected row using a Bootstrap class. 增長了代碼,動態向tr標籤添加被選講師的class=「success」。
string selectedRow = ""; if (item.ID == (int?)ViewData["InstructorID"]) { selectedRow = "success"; }
Added a new hyperlink labeled Select immediately before the other links in each row, which causes the selected instructor’s ID to be sent to the Index
method. 在每行其餘連接後面增長了一個新連接標籤Select,用於將被選講師的ID傳遞給Index方法。
<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Reordered the columns to display Last Name, First Name, Hire Date, and Office in that order. 記錄的列按照Last Name, First Name, Hire Date, Office的順序進行顯示。
Run the application and select the Instructors tab. The page displays the Location property of related OfficeAssignment entities and an empty table cell when there’s no related OfficeAssignment entity.
運行運用並選擇講師。頁面會就顯示相關OfficeAssignment實體的屬性,若是沒有相關的OfficeAssignment實體就會顯示一個空表。
In the Views/Instructor/Index.cshtml file, after the closing table element (at the end of the file), add the following code. This code displays a list of courses related to an instructor when an instructor is selected.
在Views/Instructor/Index.cshtml文件中,在關閉表格的標籤以後(在文件結尾處),添加下列代碼。當選擇了一個教師時,該代碼顯示一個相關的課程列表。
@if (Model.Courses != null) { <h3>Courses Taught by Selected Instructor</h3> <table class="table"> <tr> <th></th> <th>Number</th> <th>Title</th> <th>Department</th> </tr> @foreach (var item in Model.Courses) { string selectedRow = ""; if (item.CourseID == (int?)ViewData["CourseID"]) { selectedRow = "success"; } <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.
該部分代碼讀取視圖模型的Courses屬性進而顯示課程列表,同時也提供了Select連接,向Index方法發送被選課程的ID。
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.
運行該頁面並選擇一個講師。如今你會看到一個表格,顯示了被選講師的課程,你也會看到每一個課程相關的系的名稱。
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 class="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.
改代碼讀取視圖模型的Enrollments屬性,用於顯示已註冊該課程的學生列表。
Run the page and select an instructor. Then select a course to see the list of enrolled students and their grades.
運行該頁面並選擇一個講師。而後選擇一門課程,看一下已註冊學生的列表和相應的班級。
When you retrieved the list of instructors in InstructorsController.cs, you specified eager loading for the Courses
navigation property.
在InstructorsController.cs文件中,當你取回講師列表時,你爲Course導航屬性指定了預加載。
Suppose you expected users to only rarely want to see enrollments in a selected instructor and course. In that case, you might want to load the enrollment data only if it’s requested. To do that you (a) omit eager loading for enrollments when reading instructors, and (b) only when enrollments are needed, call the Load
method on an IQueryable
that reads the ones you need (starting in EF Core 1.0.1, you can use LoadAsync
). EF automatically 「fixes up」 the Courses
navigation property of already-retrieved Instructor entities with data retrieved by the Load
method.
假定你但願用戶在選擇的講師和課程中不多想看到註冊信息。在這種狀況下,你可能想僅在發出請求時才加載註冊日期。爲了實現這個目的,你會這樣作:
首先:當讀取講師信息時忽略註冊信息的預讀取;
其次:僅當須要註冊信息時,再以IQueryable方式調用Load方法讀取須要的一個數據(從EF Core 1.0.1開始,可使用LoadAsync了)。EF自動「修復」經Load方法已取回的講師實體數據包含的Course導航屬性。
To see this in action, replace the Index
method with the following code:
要看一下該方法,用下列代碼替換Index方法:
public async Task<IActionResult> Index(int? id, int? courseID) { var viewModel = new InstructorIndexData(); viewModel.Instructors = await _context.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses) .ThenInclude(i => i.Course) .Include(i => i.Courses) .ThenInclude(i => i.Course) .ThenInclude(i => i.Department) .OrderBy(i => i.LastName) .ToListAsync(); if (id != null) { ViewData["InstructorID"] = id.Value; Instructor instructor = viewModel.Instructors.Where( i => i.ID == id.Value).Single(); viewModel.Courses = instructor.Courses.Select(s => s.Course); } if (courseID != null) { ViewData["CourseID"] = courseID.Value; _context.Enrollments .Include(i => i.Student) .Where(c => c.CourseID == courseID.Value).Load(); viewModel.Enrollments = viewModel.Courses.Where( x => x.CourseID == courseID).Single().Enrollments; } return View(viewModel); }
The new code drops the ThenInclude method calls for enrollment data from the code that retrieves instructor entities. If an instructor and course are selected, the highlighted code retrieves Enrollment entities for the selected course. With these Enrollment entities, the code eagerly loads the Student navigation property.
新代碼捨棄了ThenInclude方法,而是從已返回的講師實體集合的代碼中調用註冊數據。若是選擇了一個講師和課程,高亮代碼取回被選課程的註冊實體信息。對於這些註冊實體,代碼預加載了學生的導航屬性。
So now, only enrollments taught by the selected instructor in the selected course are retrieved from the database.
因此如今,從數據庫中取回的只是那些在被選課程中的被選講師的講授註冊信息。
Notice that the original query on the Instructors entity set now omits the AsNoTracking
method call. Entities must be tracked for EF to 「fix up」 navigation properties when you call the Load
method.
注意對講師實體集合的原始查詢如今已經忽略了調用AsNoTarcking方法。對EF來講,調用Load方法時要「修復」導航屬性,實體必須可被跟蹤。
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.
運行講師的Index頁面,你將看到頁面上的顯示沒有什麼不一樣,儘管已經改變了如何取回這些數據的方法。
You’ve now used eager loading with one query and with multiple queries to read related data into navigation properties. In the next tutorial you’ll learn how to update related data.
如今,你已經使用預讀取的方法執行了單獨查詢和複合查詢,將關係數據讀入導航屬性。在下個教程中,你將學習如何更新關係數據。