這是微軟官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻譯,這裏是第七篇:爲ASP.NET MVC應用程序讀取相關數據html
原文:Reading Related Data with the Entity Framework in an ASP.NET MVC Application程序員
譯文版權全部,謝絕全文轉載——但您能夠在您的網站上添加到該教程的連接。web
在以前的教程中您已經完成了學校數據模型。在本教程中你將學習如何讀取和現實相關的數據——這裏指的是經過實體框架的導航屬性加載的數據。數據庫
下方的截圖顯示了你將要完成的頁面。api
實體框架擁有多種將相關數據從一個實體的導航屬性中進行加載的方法:mvc
由於延遲加載和顯式加載都不當即檢索屬性的值,因此它們也被稱爲推遲加載。app
若是你知道你當即須要每一個實體的相關數據,預先加載一般提供最佳的性能。由於單個查詢發送到數據庫並一次性獲取數據的效率一般比在每一個實體上再發出一次查詢的效率更高。例如,在上面的示例中,假定每一個繫有十個相關的課程,預先加載會致使只有一個查詢(join聯合查詢)往返於數據庫。延遲加載和顯式加載二者都將形成11個查詢和往返。在高延遲的狀況下,額外的查詢和往返一般是不利的。框架
另外一方面,在某些狀況下使用延遲加載的效率更高。預先加載可能會致使生成SQL Server不能有效處理的很是複雜的聯接查詢。或者,若是您正在處理的是須要訪問的某個實體的導航屬性,該屬性僅爲實體集的一個子集,延遲加載可能比預先加載性能更好,由於預先加載會將全部的數據所有加載,即便你不須要訪問它們。若是應用程序的性能是極爲重要的,你最好測試並在這兩種方法之間選擇一種最佳的。asp.net
延遲加載可能會屏蔽一些致使性能問題的代碼。例如,代碼沒有指定預先或顯式加載但在處理大量實體並時在每次迭代中都使用了導航屬性的狀況下,代碼的效率可能會很低(由於會有大量的數據庫往返查詢)。一個在開發環境下表現良好的應用程序可能會在移動到Windows Azure SQL數據庫時因爲增長了延遲致使延遲加載的性能降低。你應當分析並測試以確保延遲加載是不是適當的。詳細信息,請參閱Demystifying Entity Framework Strategies: Loading Related Data和Using the Entity Framework to Reduce Network Latency to SQL Azure。函數
若是你在序列化期間啓用了延遲加載,最終你可能會查詢到比預期更多的數據。序列化通常會訪問類的每一個屬性。而屬性訪問觸發延遲加載,而後會將延遲加載的實體也進行序列化。最終有可能會致使更多的延遲加載及屬性的序列化,要防止這種鏈式反應,請在實體序列化以前禁用延遲加載。
有一種避免序列化問題的方式是序列化數據傳輸對象(DTO)而不是實體對象,如Using Web API with Entity Framework教程所示。
若是您不想使用DTO,您能夠禁用延遲加載並避免經過disabling proxy creation來避免代理問題。
這裏有一些禁用延遲加載的方式:
this.Configuration.LazyLoadingEnabled = false;
Course實體包含一個導航屬性,裏面包括了分配給該課程的Department實體。若要在課程列表中顯示已分配系的名稱,你須要從Department實體中獲取Name屬性,即Course.Department導航屬性。
爲Course實體類型新建一個「包含視圖的MVC 5控制器(使用Entity Framework)」控制器並命名爲CourseController,使用在以前你建立Student控制器同樣的設置,以下圖所示:
打開該控制器並查看Index方法:
public ActionResult Index() { var courses = db.Courses.Include(c => c.Department); return View(courses.ToList()); }
你看到自動生成的腳手架使用Include方法來指定了Department屬性的預先加載。
打開Views\Course\Index.cshtml並使用下面的代碼替換原有的:
@model IEnumerable<ContosoUniversity.Models.Course> @{ ViewBag.Title = "Courses"; } <h2>Courses</h2> <p> @Html.ActionLink("Create New", "Create") </p> <table class="table"> <tr> <th> @Html.DisplayNameFor(model => model.CourseID) </th> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.Credits) </th> <th> Department </th> <th></th> </tr> @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> @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> </tr> } </table>
你對腳手架代碼做了以下更改:
請注意在系行中,腳手架代碼顯示系實體的Name屬性使經過導航屬性來加載的。
<td> @Html.DisplayFor(modelItem => item.Department.Name) </td>
運行該頁面(選擇課程選項卡)以查看系名稱列表。
在這一節中您將建立一個控制器和使用講師實體的視圖來顯示講師頁面。
此頁面經過如下方式來讀取和現實相關數據:
講師頁面顯示了三個不一樣的表格,因此您將建立一個包含三個屬性的視圖模型,每一個屬性持有一個表格所需的數據。
在ViewModels文件夾中,建立InstructorIndexData.cs並使用下面的代碼替換原來的:
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; } } }
和以前CourseController控制器同樣,建立一個InstructorController控制器,以下圖所示:
打開Controller\InstructorController.cs並添加ViewModels的命名空間引用:
@using ContosoUniversity.ViewModels;
在腳手架建立的Index方法代碼中指定只有OfficeAssignment導航屬性是預先加載的。
public ActionResult Index() { var instructors = db.Instructors.Include(i => i.OfficeAssignment); return View(instructors.ToList()); }
使用下面的代碼替換Index方法以加載其餘相關的數據:
public ActionResult Index(int? id, int? 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.ID == 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); }
該方法接收可選的路由參數(id)及一個查詢字符串參數(courseID)用來提供所選講師及課程的ID值並將全部須要的數據傳給視圖。頁面上的選擇超連接將提供這些參數。
代碼首先建立視圖模型的實例並將講師列表放進模型中,該代碼指定在OfficeAssignment和Courses導航屬性上使用預先加載。
var viewModel = new InstructorIndexData(); viewModel.Instructors = db.Instructors .Include(i => i.OfficeAssignment) .Include(i => i.Courses.Select(c => c.Department)) .OrderBy(i => i.LastName);
第二個Include方法加載課程,而且爲每一個課程預先加載Department導航屬性。
.Include(i => i.Courses.Select(c => c.Department))
如前文所述,除非是爲了提升性能,預先加載不是必須的。因爲視圖老是須要OfficeAssgnment實體,將它們在同一個查詢中進行處理使更有效的。課程實體是在當一個講師被選擇時才須要被加載的,只有頁面比沒有選擇更常常地顯示課程,預先加載才比延遲加載更好。
若是一個講師ID被選擇了,會從視圖模型的列表中來檢索所選擇講師。視圖模型的Courses屬性經過講師的Courses導航屬性來加載相關的Course實體。
if (id != null) { ViewBag.InstructorID = id.Value; viewModel.Courses = viewModel.Instructors.Where( i => i.ID == id.Value).Single().Courses; }
Where方法返回一個集合但在這裏僅僅是返回一個講師實體。Single方法將集合轉換爲一個講師實體,使您可以訪問該實體的Courses屬性。
當您知道該集合將只包含一個元素時,您可使用集合上的Single方法。當你在一個空集合或存有多個元素的集合上調用Single方法時將應發一個異常。另外一個選擇是使用SingleOrDefault,若是該集合爲空,則返回一個缺省值。但在本例中使用SingleOrDefault仍將致使異常(將嘗試訪問Courses屬性,但該屬性是一個空引用)而且異常消息會說明這點。當調用Single方法時,您還能夠經過傳遞一個Where條件而不是分別調用Where及Single方法:
.Single( i => i.ID == id.Value)
而不是
.Where( i => i.ID == id.Value).Single()
下一步,若是選擇了一個課程,將從視圖模型的課程列表中檢索所選擇的課程。而後從課程的註冊導航屬性中讀取註冊實體並加載到到視圖模型的註冊屬性中。
if (courseID != null) { ViewBag.CourseID = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where( x => x.CourseID == courseID).Single().Enrollments; }
在Views\Instructor\Index.cshtml中,使用下面的代碼替換原來的:
@model ContosoUniversity.ViewModels.InstructorIndexData @{ ViewBag.Title = "Instructor"; } <h2>Instructor</h2> <p> @Html.ActionLink("Create New", "Create") </p> <table class="table"> <tr> <th> Last Name </th> <th> First Name </th> <th> Hire Date </th> <th> Office </th> <th></th> </tr> @foreach (var item in Model.Instructors) { string selectedRow = ""; if (item.ID == ViewBag.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> @Html.ActionLink("Select", "Index", new { id = item.ID }) | @Html.ActionLink("Edit", "Edit", new { id = item.ID }) | @Html.ActionLink("Details", "Details", new { id = item.ID }) | @Html.ActionLink("Delete", "Delete", new { id = item.ID }) </td> </tr> } </table>
您對代碼作了如下更改:
<td> @if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location } </td>
string selectedRow = ""; if (item.ID == ViewBag.InstructorID) { selectedRow = "success"; } <tr class="@selectedRow">
運行應用程序,而後選擇講師選項卡,頁面上顯示了講師的信息,以及OfficeAssignment實體不爲空狀況下的Location屬性,若是沒有相關的辦公室,則什麼都不顯示。
在Views\Instructor\Index.cshtml文件中,在table元素以後添加如下代碼,用來顯示選中的講師的課程列表
@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 == ViewBag.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> }
此代碼用來讀取視圖模中的課程屬性並顯示。它還提供了一個Select超連接,用來將所選課程的ID發送給Index方法。
運行頁面並選擇一名講師,你將看到一個表格來顯示分配給該講師的課程。
在您剛纔添加的代碼以後添加下列代碼,用來顯示選擇的課程中就讀的學生列表。
@if (Model.Enrollments != null) { <h3> Students Enrolled in Select 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屬性用來顯示註冊該課程的學生。
運行頁面並選擇一名講師,而後選擇一門課程查看註冊的學生及他們的成績。
打開InstructorController.cs,檢查Index方法如何針對選擇的課程獲取註冊的列表:
if (courseID != null) { ViewBag.CourseID = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where( x => x.CourseID == courseID).Single().Enrollments; }
當您檢索到講師列表時,在Courses導航屬性及每一個課程的系屬性上您指定了預先加載。而後您將課程集合放到視圖模型中,如今你就能夠在集合的實體中經過註冊導航屬性來進行訪問。由於你沒有指定Course.Enrollments導航屬性的預先加載,該屬性中的數據將使用延遲加載,只有在呈現頁面時纔會加載。
若是你禁用延遲加載而不更改其餘的代碼,則無論實際上有多少註冊,Enrollments屬性將是空的。在這種狀況下,若是想要加載Enrollments屬性,你必須指定預先加載或顯式加載。你已經見到如何使用預先加載。爲了展現顯式加載,使用下面的代碼替換原先的學生部分,咱們將在Enrollments屬性上使用顯式加載。
if (courseID != null) { ViewBag.CourseID = courseID.Value; //延遲加載 //viewModel.Enrollments = viewModel.Courses.Where( // x => x.CourseID == courseID).Single().Enrollments; //顯式加載 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; }
在選定的Course實體後,新代碼使用顯式加載課程的Enrollments導航屬性:
db.Entry(selectedCourse).Collection(x => x.Enrollments).Load();
而後顯式加載每一個Enrollment實體相關的Student實體:
db.Entry(enrollment).Reference(x => x.Student).Load();
請注意你使用Collection方法來加載集合,但對於只有一個實體的屬性,使用Reference方法來加載。
如今從新運行頁面,確認一切都運行正常,但實際上你已經更改了數據檢索的方式。
你如今已經嘗試使用延遲、預先及顯式三種加載方式來將相關數據加載到導航屬性中,在下一節教程中,您將學習如何更新相關的數據。
Tom Dykstra - Tom Dykstra是微軟Web平臺及工具團隊的高級程序員,做家。