Contoso 大學(xué)示例 Web 應(yīng)用程序演示如何使用實體框架(EF)Core 2.0 和 Visual Studio 2017 創(chuàng)建 ASP.NET Core 2.0 MVC Web 應(yīng)用程序聪姿。 如欲了解更多本教程相關(guān)信息缕碎,請參閱 一谴轮、入門
在上一教程中挂洛,您完成了學(xué)校數(shù)據(jù)模型。在本章中超营,您將讀取和展示相關(guān)數(shù)據(jù) -- 即鸳玩,實體框架加載到導(dǎo)航屬性的數(shù)據(jù)。
以下圖片展示了您即將完成的頁面演闭。
相關(guān)數(shù)據(jù)的 Eager Loading (貪婪加載), Explicit Loading (顯式加載), 和 Lazy Loading (懶加載)
ORM (對象關(guān)系映射)框架不跟,例如說 Entity Framework, 通常有多種方式用于加載實體的導(dǎo)航屬性米碰。
- Eager loading -- 貪婪加載窝革。 當(dāng)讀取實體的時候购城,也讀取實體相關(guān)的數(shù)據(jù)。這通常導(dǎo)致一個單一連接查詢聊闯,來取出所以需要的數(shù)據(jù)工猜。在 Entity Framework 使用
Include
和ThenInclude
方法來指定貪婪加載米诉。
image.png
您可以在分離的查詢中檢索其中一些數(shù)據(jù)菱蔬, 然后讓 EF “修復(fù)” 導(dǎo)航屬性。 也就是說史侣, EF 會自動將分離查詢中的實體添加到之前讀取到的實體導(dǎo)航屬性中拴泌。 對于檢索相關(guān)數(shù)據(jù)的查詢, 您可以使用 Load 方法代替那些返回一個list
或object
的方法惊橱,比如說ToList
或Single
蚪腐。
image.png - Explicit loading -- 顯示加載。第一次讀取實體時税朴, 相關(guān)的數(shù)據(jù)沒有被檢索回季。當(dāng)您需要的時候,您需要寫代碼來檢索相關(guān)數(shù)據(jù)正林。 如同在貪婪加載中使用分離查詢一樣泡一,顯示加載將形成多個查詢送往數(shù)據(jù)庫。不同之處在于觅廓,使用顯示加載鼻忠,代碼指定的是要加載的導(dǎo)航屬性。在 Entity Framework 1.1 中您可以使用
Load
方法來執(zhí)行顯示加載杈绸。例如:
image.png - Lazy loading -- 懶加載帖蔓,或延遲加載。第一次讀取實體時瞳脓, 相關(guān)的數(shù)據(jù)沒有被檢索塑娇。但是,當(dāng)您嘗試訪問導(dǎo)航屬性時劫侧,導(dǎo)航屬性相關(guān)的數(shù)據(jù)將會被自動檢索出埋酬。當(dāng)您首次訪問導(dǎo)航屬性時,將有一個查詢發(fā)往數(shù)據(jù)庫板辽。 Entity Framework Core 1.0 不支持
Lazy loading
奇瘦。
性能注意事項
如果您事先知道,對于每個實體劲弦,需要相關(guān)的數(shù)據(jù)的話耳标,貪婪加載通常提供了最佳性能,因為發(fā)送到數(shù)據(jù)庫的一個查詢通常比多個查詢更有效率邑跪。 例如次坡,假設(shè)每個部門有十個相關(guān)的課程呼猪,貪婪加載方法使用了一條查詢加載一個部門的所有相關(guān)數(shù)據(jù),只需要一次數(shù)據(jù)庫往返砸琅。而對每個部門單獨(dú)查詢課程宋距,將導(dǎo)致出現(xiàn)十一個數(shù)據(jù)庫往返。多余的數(shù)據(jù)庫往返在延遲較高時對性能特別不利症脂。
另一方面谚赎,在某些情況下,單獨(dú)查詢會更加高效诱篷。 貪婪加載可能會導(dǎo)致非常復(fù)雜的聯(lián)結(jié)查詢以至于 SQL Server 無法有效處理壶唤。 或者,您只需要對一個實體集的某個子集訪問其導(dǎo)航屬性棕所,單獨(dú)查詢將可能比貪婪加載表現(xiàn)得更好闸盔,因為貪婪加載加載了超出您需要的數(shù)據(jù)的原因。 如果性能對您非常重要的話琳省,最好對兩種方式都進(jìn)行測試來做出最佳的選擇迎吵。
創(chuàng)建課程頁,其中顯示部門名稱针贬。
Course
實體包含一個導(dǎo)航屬性击费,對應(yīng)課程所分配部門的 Department
實體。 若要在 Course
(課程)列表中顯示所分配 Department
(部門)的名稱坚踩,您需要從 Course.Department
導(dǎo)航屬性連接的 Department
實體中取得 Name
屬性荡灾。
為 Course
實體類型創(chuàng)建一個控制器,命名為 CoursesController瞬铸,使用前面教程中用于創(chuàng)建 Students 控制器時用的腳手架批幌,相同的選項 -- “視圖使用 Entity Framework 的 MVC 控制器”。如下所示:
打開
CoursesController.cs
文件嗓节,檢查 Index
方法荧缘。腳手架已自動使用 Include
方法指定 Department
導(dǎo)航屬性為貪婪加載。將
Index
方法替換為以下代碼拦宣, 使用一個更合適的名稱命名返回 Course
實體的 IQueryable
對象截粗。
public async Task<IActionResult> Index()
{
var courses = _context.Courses
.Include(c => c.Department)
.AsNoTracking();
return View(await courses.ToListAsync());
}
打開 Views/Courses/Index.cshtl
,并使用以下代碼替換模板代碼鸵隧。
@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.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Credits)
</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.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</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>
您對腳手架生成的代碼作了如下更改:
- 修改了課程
Index
頁面的標(biāo)題 - 添加了一列用于顯示
CourseID
屬性绸罗。 默認(rèn)情況下, 主鍵不會出現(xiàn)在腳手架代碼中豆瘫,因為它們通常對最終用戶無意義珊蟀。 然而, 在這兒外驱,主鍵是有意義的育灸,您打算顯示出來腻窒。 - 修改
Department
列以顯示部門名稱。 代碼顯示加載到Department
導(dǎo)航屬性中Department
實體的Name
屬性磅崭。
@Html.DisplayFor(modelItem => item.Department.Name)
運(yùn)行應(yīng)用程序儿子,選擇 Course
菜單以查看含有部門名稱的列表。
創(chuàng)建一個教師頁面砸喻,其中顯示課程及學(xué)生注冊情況
在本節(jié)中柔逼,您將會為 Instructor
實體創(chuàng)建一個控制器和視圖用于展示教師。
本頁面使用以下方法讀取并展示相關(guān)數(shù)據(jù):
- 教師列表展示來自
OfficeAssignment
實體的相關(guān)數(shù)據(jù)恩够。Instructor
和OfficeeAssignment
實體是 一 對 零或一 關(guān)系卒落,對OfficeAssignment
實體將使用貪婪加載方式。 如前所述蜂桶, 當(dāng)您需要主表所有行的相關(guān)數(shù)據(jù)時,貪婪加載是最高效的也切。 在這種情況下扑媚, 你希望顯示所有教師分配的辦公室。 - 當(dāng)用戶選擇一個教師時雷恃,相關(guān)的課程實體將會顯示疆股。 教師和課程實體是 “多對多” 關(guān)系。您將對課程及相關(guān)的部門實體使用貪婪加載倒槐。此時旬痹,單獨(dú)的查詢可能會更加高效,因為您只需要所選擇教師相關(guān)的課程讨越。 不過两残,這個示例主要用于展示如何對導(dǎo)航屬性使用貪婪加載,以及對導(dǎo)航屬性內(nèi)的實體進(jìn)行貪婪加載把跨。
- 當(dāng)用戶選擇一個課程時人弓,相關(guān)的注冊實體將會顯示。 課程和注冊實體是 “一對多” 關(guān)系着逐。 您將會使用單獨(dú)的查詢來應(yīng)對注冊實體和相關(guān)的學(xué)生實體崔赌。
創(chuàng)建教師索引視圖的視圖模型
教師頁顯示三個不同的表中的數(shù)據(jù)。因此耸别,你創(chuàng)建的視圖模型將包括三個屬性健芭,每個屬性包含一個表的數(shù)據(jù)。
在 SchoolViewModels 文件夾中秀姐,創(chuàng)建 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; }
}
}
創(chuàng)建教師控制器和視圖
使用包含 EF 讀/寫 操作的控制器模板創(chuàng)建一個教師控制器慈迈,如下圖所示:
打開 InstructorsController.cs 和添加 Viewmodel 命名空間引用:
using ContosoUniversity.Models.SchoolViewModels;
使用以下代碼替換 Index 方法,以達(dá)到相關(guān)數(shù)據(jù)的貪婪加載囊扳,并放入視圖模型中吩翻。
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.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.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.CourseAssignments.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);
}
方法接受可選路由數(shù)據(jù)(id)和一個查詢字符串參數(shù)(courseID)兜看,分別對應(yīng)選擇的教師和選擇的課程。參數(shù)從頁面的超鏈接中而來狭瞎。
代碼首先創(chuàng)建一個視圖模型的實例细移,并在其中加入教師列表。 代碼指定對 Instrator.OfficeAssignment
和 CourseAssignments
導(dǎo)航屬性使用貪婪加載熊锭。 在 CourseAssignments
屬性中弧轧,Course
屬性將被加載, 然后在 Course
屬性中碗殷, Enrollments
和 Department
屬性將會被加載精绎,同時在每個 Enrollment
實體中, Student
屬性將會被加載锌妻。
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
由于視圖始終需要 OfficeAssignmet
實體代乃,因此在同一個查詢中獲取它更有效。 當(dāng)在網(wǎng)頁中選擇教師時仿粹,課程實體是必需的搁吓,因此只有當(dāng)頁面以不是沒有選擇的課程更頻繁地顯示時,單個查詢才會比多個查詢更好吭历。
代碼中堕仔,CourseAssignments
和 Course
重復(fù)出現(xiàn),因為您需要 Course
的兩個屬性晌区。 在第一個 ThenInclude
中獲取 CourseAssignment.Course
, Course.Enrollements
, 及 Enrollment.Student
摩骨。
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
在代碼中的那一點(diǎn),另一個 ThenInclude
將用于學(xué)生的導(dǎo)航屬性朗若,您不需要它恼五。 但是,調(diào)用 Include
是由 Instructor
屬性開始的捡偏,所以你必須重新遍歷整個鏈條唤冈,通過指定 Course.Department
而不是 Course.Enrollments
。
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
在選擇了一個教師時银伟,將執(zhí)行下面的代碼你虹。 從教師視圖模型中的列表中檢索所選的教師。 然后視圖模型的Courses
屬性和課程實體從該教師的 CourseAssignments
導(dǎo)航屬性中一起被加載彤避。
if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
Where
方法返回一個集合傅物,但在本例中,傳遞給該方法的條件只會返回一個 Instructor
實體琉预。 Single
方法將集合轉(zhuǎn)換為單個 Instructor
實體董饰, 這樣一來,您就可以訪問該實體的 CourseAssignments
屬性。 CourseAssignments
包含 CourseAssignments
實體集合卒暂,從中得到相關(guān)的 Course
實體集啄栓。
當(dāng)您知道集合將只有一個項目時,您可以在集合上使用 Single
方法也祠。如果傳遞給它的集合為空昙楚,或者有多個項目, Single
方法會拋出異常诈嘿。一個替代方法是 SingleOrDefault
堪旧,如果集合是空的,它返回一個默認(rèn)值(在這種情況下為null)奖亚。 但是淳梦,在這種情況下,仍然會導(dǎo)致異常(嘗試在空引用上查找Courses屬性)昔字,并且異常消息將不太清楚地指出問題的原因爆袍。 當(dāng)您調(diào)用 Single
方法時,您還可以傳遞 Where
條件李滴,而無需單獨(dú)調(diào)用 Where
方法:
.Single(i => i.ID == id.Value)
而不是
.Where(I => i.ID == id.Value).Single()
接下來螃宙,如果選擇課程,則從視圖模型中的課程列表中檢索所選課程所坯。 然后,視圖模型的 “Enrollments” 屬性將加載該課程的 “Enrollments” 導(dǎo)航屬性中的 “Enrollment” 實體挂捅。
if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
修改 “教師索引” 視圖
在 Views/Instructors/Index.cshtml 文件中芹助,使用以下代碼替換。
@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.CourseAssignments)
{
@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>
你對現(xiàn)有代碼做出以下更改:
- 修改頁面 model 類為
InstructorIndexData
闲先。 - 修改頁面標(biāo)題為
Instructors
状土。 - 在
item.OfficeAssignment
不為空的情況下,才添加一個Office
列伺糠,顯示item.OfficeAssignment.Location
蒙谓。 (因為這是一個 "一 對 零或一" 的關(guān)系,可能沒有相關(guān)的OfficeAssignment
實體训桶。)@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
- 添加了一個課程列累驮,顯示每個教師所教授的課程。 請參閱 使用 @: 的顯式行轉(zhuǎn)換 有關(guān)此 Razor 語法的更多信息舵揭。
- 添加的代碼動態(tài)地將
class =“success”
添加到所選教師的tr
元素中谤专。 這將會通過 Bootstrap 類為選擇行設(shè)置一個背景顏色。string selectedRow = ""; if (item.ID == (int?)ViewData["InstructorID"]) { selectedRow = "success"; } <tr class="@selectedRow">
- 在每行中的其他鏈接前午绳,添加一個新的超鏈接 "Select" 置侍,將所選教師的
ID
發(fā)送到Index
方法。<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
運(yùn)行應(yīng)用程序,選擇 Instructor
鏈接蜡坊。 當(dāng)沒有相關(guān)的 OfficeAssignment
實體時杠输,該頁面顯示相關(guān) OfficeAssignment
實體的 Location
屬性和一個空表單元格。
在
Views/Instructors/Index.cshtml
文件中秕衙,在 </table> 標(biāo)簽(文件末尾)后添加以下代碼蠢甲。 該代碼顯示了當(dāng)教師選擇時與教練相關(guān)的課程列表。
@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>
}
此代碼讀取視圖模型的 Courses
屬性以顯示課程列表灾梦。它還提供一個選擇超鏈接峡钓,將所選課程的 ID
發(fā)送到 Index
操作方法。
刷新頁面并選擇一個教練若河。 現(xiàn)在能岩,您看到一個網(wǎng)格,顯示分配給所選教師的課程萧福,并且每個課程都會看到所分配部門的名稱拉鹃。
在您剛剛添加的代碼塊之后,添加以下代碼鲫忍。 這將顯示在選擇課程時注冊該課程的學(xué)生列表膏燕。
@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>
}
該代碼讀取視圖模型的 Enrollments
屬性,以顯示在課程中注冊的學(xué)生列表悟民。
再次刷新頁面并選擇一個教師坝辫。 然后選擇一個課程,查看注冊學(xué)生及其成績的列表射亏。
顯式加載
當(dāng)您在 InstructorsController.cs
中檢索到教師列表時近忙,您為 CourseAssignments
導(dǎo)航屬性指定了貪婪加載。
假設(shè)您期望用戶很少想要在選定的教練和課程中看到注冊智润。 在這種情況下及舍,您可能只需要請求加載注冊數(shù)據(jù)。 要查看如何進(jìn)行顯式加載的示例窟绷,請使用以下代碼替換 Index
方法锯玛,這將刪除 Enrollments
的貪婪加載然后顯式加載該屬性。
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.CourseAssignments)
.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.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
}
viewModel.Enrollments = selectedCourse.Enrollments;
}
return View(viewModel);
}
新代碼從用于檢索教師實體的代碼中刪除 Enrollment
數(shù)據(jù)的 ThenInclude
方法調(diào)用兼蜈。如果選擇了教員和課程攘残,高亮部分的代碼將檢索所選課程的注冊實體,以及每個注冊的學(xué)生實體饭尝。
運(yùn)行應(yīng)用程序肯腕,選擇 Instructor
鏈接。 可以看到钥平,雖然您已經(jīng)更改了數(shù)據(jù)的檢索方式实撒, 頁面上顯示的內(nèi)容并沒有任何區(qū)別于之前的姊途。
小結(jié)
您現(xiàn)在已經(jīng)使用貪婪加載,在一個查詢及多個查詢中用于讀取相關(guān)數(shù)據(jù)到導(dǎo)航屬性知态。 在下一個教程中捷兰,您將學(xué)習(xí)如何更新相關(guān)數(shù)據(jù)。