EntityFramework_MVC4中EF5 新手入门教程之五 ---5.通过 Entity Framework 读取相关数据
在前面的教程中,您完成School数据模型。在本教程中,您会读取和显示相关的数据 — — 那就是,实体框架将加载到导航属性的数据。
下面的插图显示页面,您将完成的工作。
延迟、 预先,和显式加载的相关数据
有实体框架可以将相关的数据加载到一个实体的导航属性的几种方法:
- 延迟加载。当第一次读的实体时,并不被检索相关的数据。然而,第一次尝试访问导航属性,该导航属性所需的数据是自动检索。这将导致多个查询发送到数据库 — — 一个用于该实体本身,一个必须检索每个相关的实体的数据的时间。
-
预先加载。当读取时的实体时,与它一起检索相关的数据。这通常会导致单个联接查询中检索所有需要的数据。可以通过使用
Include
方法指定预先加载。 -
显式加载。这是类似于延迟加载,除非您显式检索代码 ; 中的相关的数据当您访问一个导航属性时,它不会自动发生。通过获取实体对象状态管理器条目并调用
Collection.Load
方法用于收集或持有一个单一的实体的属性的Reference.Load
方法,则手动加载相关的数据。(在下面的示例中,如果你想要加载的管理员的导航属性,则会替换Collection(x => x.Courses)
Reference(x => x.Administrator)
.)
因为他们不立即检索的属性值,延迟加载、 显式加载也都被称为延迟加载.
一般情况下,如果您知道您需要所检索的每个实体的相关数据,那么预先加载可提供最佳的性能,因为向数据库发送单个查询通常要比所检索的每个实体都有各自的查询更为高效。例如,在上面的示例中,假设每个部门有十个相关的课程。预先加载示例会导致只是一个单 (加入) 查询和单个往返到数据库。延迟加载、 显式加载示例将两者都造成十一查询和十一个往返行程到数据库。额外的往返行程到数据库时延迟很高,尤其不利于性能。
另一方面,在某些情况下延迟加载是更有效率的。预先加载可能会导致非常复杂联接来生成,SQL Server 不能有效地处理。或如果你需要访问某个实体的导航属性只为实体集的子集您正在处理,延迟加载可能会表现得更好,因为预先加载会检索更多比您需要的数据。如果性能是至关重要的它是最佳性能测试两种方式来做出最佳的选择。
通常你会使用显式加载,只有当你渡过了延迟加载了。当你应该打开延迟加载关闭的一个方案是在序列化期间。延迟加载和序列化不拌匀,和如果你不小心到头来你可以查询更多的数据比你预期时偷懒启用加载。序列化通常可通过访问每个类型的实例上的属性。属性访问触发延迟加载,并且那些懒加载的实体进行序列化。序列化进程然后访问每个属性的懒加载实体,有可能导致更多的延迟加载和序列化。为了防止这个离家出走的链反应,变成懒加载关闭之前您序列化的实体。
默认情况下,数据库上下文类进行延迟加载。有两种方法来禁用延迟加载:
- 对于特定的导航属性,省略
virtual
关键字,当您将该属性声明。 - 对于所有的导航属性,将
LazyLoadingEnabled
设置为false
。例如,你可以把下面的代码在您的上下文类的构造函数:this.Configuration.LazyLoadingEnabled = false;
延迟加载可以掩盖导致性能问题的代码。例如,不指定渴望或显式加载但处理大批量的实体,并在每次迭代中使用几个导航属性的代码可能会效率很低 (因为许多往返到数据库)。具有良好的开发使用上的前提下 SQL server 的应用程序可能出现性能问题时搬到了 Windows Azure SQL 数据库由于增加的延迟和延迟加载。分析与实际测试负载的数据库查询,这将帮助您确定延迟加载是否适当。详细信息请参阅解密实体框架战略: 加载相关数据和使用实体框架对减少网络延迟到 SQL Azure.
创建课程Index页,展示部门名称
Course
实体包含一个导航属性包含课程指派给该署各Department
实体。若要显示分配部门名称的课程列表中,您需要得到Department
实体 (即Course.Department
导航属性Name
属性。
创建命名为该Course
的实体类型,使用相同的选项,你较早前为Student
的控制,如下面的插图所示的CourseController
控制器 (除了与不同的图像,您的上下文类是 DAL 命名空间,不包括模型命名空间中):
打开Controllers\CourseController.cs ,然后看看Index
法:
public ViewResult 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>
<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>
搭建的代码进行了以下更改:
- 索引标题转为课程.
- 向左移动行链接。
- 添加一列数字显示
CourseID
属性值的标题下。(默认情况下,主键不被搭建因为他们通常向最终用户毫无意义。然而,在这种情况下,主键是有意义和您想要显示它。) - DepartmentID (
Department
实体的外键的名称) 的最后一列标题转为部.
请注意最后一列,搭建的代码显示各Department
实体加载到Department
导航属性的Name
属性:
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
运行页 (选择在 Contoso 大学主页上的课程选项卡) 以查看与部门名称列表。
创建显示课程和招生导师索引页
在这一节您将创建一个控制器和Instructor
实体视图显示教官索引页:
此页面读取和显示相关的数据通过以下方式:
- 教师的列表将显示来自
OfficeAssignment
实体的相关的数据。Instructor
和OfficeAssignment
的实体是一个为零或一个关系。您可以使用预先加载OfficeAssignment
实体。如前所述,预先加载是通常效率更高,当你需要的相关的数据为表的主键的所有检索行。在这种情况下,您想要显示所有显示导师的办公室分配。 - 当用户选择一个教练时,相关的
Course
实体的显示。Instructor
和Course
的实体是多对多关系。您将使用预先加载Course
实体和它们相关的Department
实体。在这种情况下,延迟加载可能会更有效,因为你需要课程只为所选教师。然而,此示例演示如何使用预先加载的是自己在导航属性的实体内的导航属性。 - 当用户选择一门课程时,显示来自
Enrollments
实体集的相关的数据。Course
和Enrollment
的实体是一个一对多关系。您将添加显式加载Enrollment
实体和它们相关的Student
实体。(显式加载不必要,因为启用了延迟加载,但这演示如何显式加载。)
为Instructor的Index视图中创建一个视图模型
指导员索引页显示三个不同的表。因此,您将创建的视图模型包含三个属性,每个持有其中一个表的数据。
在Viewmodel文件夹中,创建InstructorIndexData.cs和现有的代码替换为以下代码:
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; }
}
}
为所选行添加一个样式
若要将选定的行标记需要不同的背景色。要为此用户界面提供一种风格,请将下面突出显示的代码添加到节/* info and errors */
在Content\Site.css,如下所示:
/* info and errors */
.selectedrow
{
background-color: #a4d4e6;
}
.message-info {
border: 1px solid;
clear: both;
padding: 10px 20px;
}
创建 Instructor控制器和视图
创建InstructorController
控制器,如下面的插图中所示:
打开Controllers\InstructorController.cs并添加为ViewModels
命名空间的using
语句:
using ContosoUniversity.ViewModels;
Index
方法中搭建的代码指定预先加载仅为OfficeAssignment
导航属性:
public ViewResult 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.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);
}
该方法接受可选路由数据 (id
),并提供所选的教师和所选的课程的 ID 值的查询字符串参数 (courseID
),并将所有所需的数据传递给视图。所选择的超链接在页面上提供的参数。
该代码首先创建视图模型的实例,把教师的列表。该代码指定预先加载Instructor.OfficeAssignment
和Instructor.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
方法加载课程,而且每门课程都加载它预先加载的Course.Department
导航属性的。
.Include(i => i.Courses.Select(c => c.Department))
正如前面提到的预先加载不是必需的但做是为了提高性能。由于视图总是需要OfficeAssignment
实体,它是更有效地在同一查询中取。当教练选定,在 web 页中,以便预先加载优于延迟加载,只有当与某一课程没有选择比更经常显示的页,则要求Course
各实体。
如果选择了教练的 ID,将从辅导员在视图模型中的列表中检索所选的教师。视图模型Courses
属性然后加载与Course
实体从那教练Courses
导航属性。
if (id != null)
{
ViewBag.InstructorID = id.Value;
viewModel.Courses = viewModel.Instructors.Where(i => i.InstructorID == id.Value).Single().Courses;
}
Where
方法返回一个集合,但在这种情况下条件传递到该方法结果中只有一个单一的Instructor
实体正在返回。Single
方法将集合转换为一个单一的Instructor
实体,该实体Courses
属性使您能够访问。
当您知道该集合将只包含一个项目,可使用在一个集合上的Single方法。Single
方法引发一个异常,如果传递给它的集合为空,或者如果有多个项目。另一种是SingleOrDefault,而如果集合为空,则返回默认值 (null
在这种情况下)。然而,在这种情况下,仍然会导致 (从试图找到一个Courses
属性上一个null
引用)异常,异常消息表示不清楚表明这一问题的原因。当你调用Single
方法时,你也可以传递Where
条件而不是调用Where
方法:
.Single(i => i.InstructorID == id.Value)
代替:
.Where(I => i.InstructorID == id.Value).Single()
接下来,如果选择了一门课程,从视图模型中的球场列表中检索所选的课程。然后视图模型Enrollments
属性加载与Enrollment
实体从那门课Enrollments
导航属性。
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 = "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> @Html.DisplayFor(modelItem => item.HireDate) </td> <td> @if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location } </td> </tr> } </table>
对现有代码进行了以下更改:
- 示范课改
InstructorIndexData
. - 索引页标题转为教官.
- 向左移动行链接列。
- 删除FullName列。
- 添加显示
item.OfficeAssignment.Location
仅item.OfficeAssignment
不是 null。(因为这是一个为零或一个关系,有可能不是相关的OfficeAssignment
实体。)<td> @if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location } </td>
- 添加了代码,将动态地添加
class="selectedrow"
到所选教师的tr
元素。这将设置为使用您先前创建的 CSS 类的所选行的背景色。(valign
属性将是在下面的教程中有用当向表中添加多行的列。string selectedRow = ""; if (item.InstructorID == ViewBag.InstructorID) { selectedRow = "selectedrow"; } <tr class="@selectedRow" valign="top">
- 添加新的
ActionLink
标记选择紧接之前导致选的教师 ID 发送到Index
方法的每一行中的其他链接。
运行该应用程序并选择教官选项卡。该页面显示相关的OfficeAssignment
实体和空的表格单元格的Location
的属性时没有相关的OfficeAssignment
实体。
在Views\Instructor\Index.cshtml文件中,结束后 (在文件的末尾), table
元素中添加以下突出显示的代码。这将显示有关教练的教练选中课程列表。
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
</tr>
}
</table>
@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>
}
这段代码读取视图模型显示列表的课程Courses
属性。它还提供一个Select
的超链接,将所选课程的 ID 发送到Index
操作方法。
注.Css文件由浏览器缓存。如果你看不到所做的更改,当您运行应用程序时,不要硬的刷新 (按住 CTRL 键同时单击刷新按钮,或按 CTRL + F5)。
运行此页并选择导师。现在,你看到一个网格,显示分配给所选的教师的课程,每门课程你请参阅分配部门的名称。
在您刚添加的代码块后, 添加以下代码。这显示那些被录取的学生名单在课程中选中那门课。
@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>
}
这段代码读取视图模型的Enrollments
属性显示在这门课程的学生名单。
运行此页并选择导师。然后,选择一门课程,若要查看列表的学生和他们的成绩。
添加显式加载
打开InstructorController.cs ,看看如何Index
方法获取列表中所选的课程的招生人数:
if (courseID != null)
{
ViewBag.CourseID = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
当您检索到教师的列表时,您指定预先加载Courses
导航属性,每个课程的Department
属性。然后你把Courses
集合在视图模式下,和现在你正在访问Enrollments
导航属性从该集合中的一个实体。因为您没有指定预先加载Course.Enrollments
导航属性,该属性中的数据出现在由于延迟加载页面。
如果你禁用延迟加载而无需更改代码以任何其他方式,Enrollments
属性将为 null 无论课程实际上有多少注册。在这种情况下,若要加载的Enrollments
属性,就必须指定预先加载或显式加载。您已经看到如何做预先加载。为了看到示例显式加载,用下面的代码显式加载Enrollments
属性替换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.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);
}
所选的Course
实体后,新的代码显式加载该课程Enrollments
导航属性:
db.Entry(selectedCourse).Collection(x => x.Enrollments).Load();
然后,它显式加载每个Enrollment
实体相关的Student
实体:
db.Entry(enrollment).Reference(x => x.Student).Load();
请注意您使用Collection
的方法加载集合属性,但对于认为只是一个实体的属性,您使用的Reference
方法。您现在可以运行教练索引页,你会看到什么显示在页面上,无差异,虽然您已经更改了如何检索数据。
摘要
您现在已经使用所有三种方法 (延迟、 预先和显式) 相关的数据加载到导航属性。在下一篇教程中你将学习如何更新相关的数据。