原文 Contoso 大学 - 5 – 读取关联数据

By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Microsoft's Web Platform & Tools Content Team.

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application

全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用

在前面的课程中已经完成了 School 数据模型。在这次的课程中,将要读取和显示相关的数据,这里指的是 EF 通过导航属性加载的数据。

下面的截图展示了你将好创建的页面。

5 – 1  延迟,饿汉,以及显式加载关联数据

EF 有多种方式可以通过导航属性加载关联的数据。

  • 延迟加载 Lazy Loading。当实体第一次读取的时候,关联的数据并不会被获取。 实际上,当第一次你实际访问关联属性的时候,被导航属性关联的数据才会被自动的读取。 这可能导致多次查询被发送到数据库 – 一次是读取实体本身, 对于关联的每个实体也需要分别读取。

  • 饿汉加载  Eager Loaing。当实体加载的时候,相关联的数据也一起被加载。典型地用在一次连接查询返回所有需要的相关数据,通过使用 Include 方法实现饿汉加载。

  • 显式加载 Explict Loading。这种方式类似于延迟加载,除了需要在代码中显式获取数据。在你访问导航属性的时候,不会出现自动加载。你自己手动加载关联的数据,通过访问对象状态管理器来获取实体,调用 Collection.Load 方法获取集合,或者通过调用持有单个实体的属性的 Reference.Load 方法。( 在下面的示例中,如果你希望加载 Administrator 导航属性,你应该将 Collection( x=>x.Course  ) 替换为 Reference( x=>x.Administrator ) 。

因为不会立即获取关联属性的值,延迟加载和显式加载又被称为延后加载。

一般来说,如果你知道你需要每个实体的关联属性,饿汉加载提供了最好的性能。因为只有一次查询被发送到数据库,比对每个实体都要向数据库发出一次查询要更加有效。例如,在上面的例子中,假设每个系都有相关的课程,饿汉加载只需要一次联合查询就可以获得。而使用延迟加载或者显式加载则需要 11 次查询。

从另外的角度来说,如果你不常访问实体的导航属性,或者仅仅访问一小部分实体的导航属性,延迟加载更加有效,因为饿汉加载会加载更多地不必要的数据。通常情况下,在关闭了延迟加载的情况下使用显式加载。一个关闭延迟加载的场景是在进行序列化的时候,当你知道不需要所有的导航属性数据加载。如果延迟加载启用,所有的导航属性将会自动加载,因为序列化会访问所有的属性。

数据库上下文默认支持延迟加载,有两种方法可以关闭延迟加载:

  • 对于特定的导航属性,在定义属性的时候取消 virtual
  • 对于所有的导航属性,设置 LazyLoadingEnabled 为假。

延迟加载可能导致性能问题,例如,代码中没有指定使用饿汉加载或者显式加载,但是在处理大量实体的时候,遍历每个实体并访问其导航属性可能导致低效率 ( 因为多次访问数据库 ), 但是使用延迟加载不会出现问题。在代码使用延迟加载的时候临时禁用延迟加载可能导致出现问题。因为导航属性为 null 而导致代码访问对象失败。

5 -2  创建显示系名称的课程页面

课程 Course实体包含一个所属系 Department 的导航属性,为了显示课程所属系的名称,你需要通过课程所属的系 Department 导航属性来获取系的名称 Name。

为课程实体 Course 创建一个控制器,使用与前面的学生 Student 相同的设置,如下图所示:

打开 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>

这段代码对脚手架代码做了如下的修改:

  • 将标题从 Index 修改为 Course
  • 将行的链接移到了左边
  • 在列 Number 中显示了 CourseID 属性的值。( 脚手架不生成主键,因为通常没有字面的意义。在这里我们希望显示这个值而已 )
  • 将最后一列标题从 DepartmentId 修改为 Department ( 系实体中的系名 )

注意,脚手架代码显示通过导航属性 Department 加载的系实体的 Name 属性值。

<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>

重新运行这个页面,( 在 Contoso 大学的首页中选择 Courses )来显示系名称的列表。

5-3  创建显示课程和注册信息的教师页面

在这一节中,我们创建控制器和视图来显示教师实体。

这个页面使用下面的途径来读取和显示关联的数据:

  • 教师列表中的办公室分配 OfficeAssignment 实体。教师实体与办公室分配之间是一对一或者一对零的关系,你将使用饿汉模式来加载办公室分配实体。从前所述,饿汉模式适合于当你需要主键表关联数据的时候,在这里,你需要显示所有教师的办公室分配。
  • 当用户选中一个教师的时候,需要显示这个教师相关的课程实体。教师和课程之间存在多对多的关系。你将使用饿汉模式加载课程和相关的系实体。在这里,延迟加载可能更加有效,因为仅仅需要显示选中的教师的课程,实际上,这个例子展示了如何使用饿汉模式加载导航属性中的导航属性。
  • 当用户选择课程之后,相关的注册实体 Enrollments 将会显示出来。Course 和 Enrollment 实体存在一对多的关系。你将使用显式加载来处理 Enrollment 实体,以及相关的学生 Student 实体。( 由于默认支持延迟加载,所以显示加载不是必须的。这里专门演示显式加载 )

5-3-1  创建教师页面的视图模型

教师页面显示三个不同的表。因此,需要创建一个新的视图模型,通过三个属性表示出来,每一个持有一张表的数据。

在 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; }
}
}

5-3-2  对选中的行增加一个样式

需要通过不同的背景色来标识选中的行,为 UI 提供一种新的样式,将下面的代码增加到 Content/Site.css 文件中标记为 MISC 的节中,如下所示。

/* MISC
----------------------------------------------------------*/
.selectedrow
{
background-color: #EEEEEE;
}

5-3-3  创建教师控制器和视图

为教师实体类型创建一个控制器。使用类似前面 Student 控制器的方式创建,如下所示:

打开 Controllers\InstructorController.cs ,为 ViewModels 命名空间增加  using 引用。

using ContosoUniversity.ViewModels;

脚手架生成的代码仅仅对 OfficeAssignment 导航属性使用饿汉加载模式。

public ViewResult Index()
{
var instructors = db.Instructors.Include(i => i.OfficeAssignment);
return View(instructors.ToList());
}

使用下面的代码替换原有的 Index 方法,读取关联的数据,通过 ViewModel 来保存。

public ActionResult Index(Int32? id, Int32? 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 和选中的课程,然后将所有需要的数据传递给视图。查询串通过页面上的 Select 超级链接提供。

代码首先创建 ViewModel 的实例,然后将教师实体列表保存在其中。

var viewModel = new InstructorIndexData();
viewModel.Instructors = db.Instructors
.Include(i => i.OfficeAssignment);
.Include(i => i.Courses.Select(c => c.Department))
.OrderBy(i => i.LastName);

代码使用饿汉模式加载 Instructor.OfficeAssignment 和 Instructor.Courses 导航属性。对于关联的 Course 实体,通过在 Inclue 中使用 Select 方法饿汉模式加载,结果使用 LastName 进行排序。

如果某个教师被选中了,选中的教师从 ViewModel 中的教师列表中被选出。视图模型的 Courses 属性通过教师的 Courses 属性加载相关的课程 Course 实体。

if (id != null)
{
ViewBag.InstructorID = id.Value;
viewModel.Courses = viewModel.Instructors.Where(i => i.InstructorID == id.Value).Single().Courses;
}

Where 方法返回一个集合,但是这里的情况将仅仅返回一个教师实体,Single 方法将集合转化成一个单个的实体,以便访问这个实体的 Course 属性。

在你知道集合中仅仅包含一个实体的时候,可以使用 Single 方法。Single 方法在集合中为空的时候将会抛出异常,或者在集合中包含多于一个实体的时候也会抛出异常。另外一个替换的方法是 SingleOrDefault 方法,在集合为空的时候,这个方法返回 null。实际上,在这里还是会抛出异常 ( 试图在空引用上访问 Courses 属性的时候 ),异常的信息将会简单地说明这个问题,在调用 Single 方法的时候,还可以传递一个条件来代替通过 Where 传递的条件。

.Single(i => i.InstructorID == id.Value)

替换掉:

.Where(I => i.InstructorID == id.Value).Single()

下一步,如何选中了一个课程 Course,选中的课程从视图模型 ViewModel 的 Courses 属性中获取,然后,模型的 Enrollments 属性通过课程对象的 Enrollments 导航属性被加载。

if (courseID != null)
{
ViewBag.CourseID = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(x => x.CourseID == courseID).Single().Enrollments;
}

最后,模型被传递到视图。

return View(viewModel);

5-3-4  修改教师 Instructor 视图

打开 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>
@String.Format("{0:d}", item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
</tr>
}
</table>

我们对原有的代码做了如下的变动:

  • 将标题从 Index 替换成Instructors
  • 将行的链接移到了左边
  • 删除了 FullName 列
  • 增加了 Office 列,仅在 item.OfficeAssignment 非空的时候显示 item.OfficeAssignment.Location 属性。( 这里是一对一或者一对零的关系,可能没有关联的 OfficeAssignment 实体 )
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
  • 对选中教师对应行的 tr 元素,通过代码动态增加样式 class=”selectedrow”。这里通过前面创建的样式类对选中的行设置背景色。( 在你在表中增加多行的列时, valign 属性非常有用 )
string selectedRow = "";
if (item.InstructorID == ViewBag.InstructorID)
{
selectedRow = "selectedrow";
}
<tr class="@selectedRow" valign="top">
  • 在其他链接的前面,增加了一个名为 Select 的新的 ActionLink ,用来将选中的教师 Id 传递到 Index 方法。

运行页面,查看教师列表,页面上显示了教师相关的 OfficeAssignment 导航属性的 Location 属性值,如果没有相关的办公室则显示为空。

如果 Views\Instructor\Index.cshtml 文件还打开,在 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>
}

代码读取 ViewModel 的 Courses 属性来显示课程列表。同时还提供了 Select 链接用来发送选中的课程 Id 给 Index 方法。

运行页面,选中一个教师,现在可以显示这个教师的课程列表,可以看到每个课程所属的系。

注意,如果选中的行没有被高亮显示,刷新一下浏览器,可能需要重新加载页面相关的样式表文件。

在刚刚增加的代码块之后,增加如下的代码,用来显示注册到选中课程的学生列表。

@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 属性来显示注册到课程的学生列表,DisplayFor 方法住手方法用来是的在成绩为 null 的时候显示 “No grade”,如在这个属性的 DisplayFormat 特性中定义的那样。

运行页面,选中教师,然后选中一个课程来查看注册课程的学生和他们的成绩。

5-3-5  增加显式加载

打开InstructorController.cs 文件,查看Index 方法如何获取注册学生的列表。

if (courseID != null)
{
ViewBag.CourseID = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(x => x.CourseID == courseID).Single().Enrollments;
}

在获取教师列表的时候,使用饿汉模式加载 Courses 导航属性值,以及 Department 导航属性的值。然后将结果保存到视图模型的 Courses 集合中,再从这个集合的一个实体中访问注册实体。因为没有对Course.Enrollements 属性指定饿汉加载,出现在页面上时将使用延迟加载。

如果仅仅禁用延迟加载而不采取其他的措施,Enrollments 属性将是 null ,而不管实际上有多少注册。在这种情况下,就必须要么指定饿汉加载,要么指定显式加载。你已经见到了如何使用饿汉加载,因为展示如何使用显式加载,将 Index 方法中替换为如下的代码,这里使用显式加载来读取 Enrollments 属性。

public ActionResult Index(Int32? id, Int32? 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 方法。再次运行程序,显示的页面并没有什么不同,虽然已经修改了获取数据的方式。

现在,你已经使用了三种加载方式 ( 延迟,饿汉,显式 )来加载导航属性相关的数据,下一次,我们将学习如何更新相关的数据。

Contoso 大学 - 5 – 读取关联数据的更多相关文章

  1. Contoso 大学 - 6 – 更新关联数据

    原文 Contoso 大学 - 6 – 更新关联数据 By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Microsoft's ...

  2. 读取关联数据(EF Core2.1.1)

    对象-关系映射框架比如EF有三种 方式使用 模型中的导航属性来加载关联数据. 一..Lazy Loading.(关联数据在访问导航属性时被透明的加载,不需要特别的代码,自动的加载) 当一个实体第一次读 ...

  3. EF学习笔记(七):读取关联数据

    总目录:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 本篇参考原文链接:Reading Related Data 本章主要讲述加载显示关联数据: 数据加载分为以下三种 Lazy l ...

  4. 【EF6学习笔记】(七)读取关联数据

    本篇参考原文链接:Reading Related Data 本章主要讲述加载显示关联数据: 数据加载分为以下三种 Lazy loading 这种加载方式在于需要用到这个导航属性数据的时候,才会去数据库 ...

  5. EF学习笔记(八):更新关联数据

    学习笔记主目录链接:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 上一篇链接:EF学习笔记(七):读取关联数据 本篇原文链接:Updating Related Data 本篇主要考 ...

  6. 【EF6学习笔记】(八)更新关联数据

    上一篇链接:EF学习笔记(七):读取关联数据 本篇原文链接:Updating Related Data 本篇主要考虑对于有关联的数据进行新增.删除.更新操作:比如Course .Instructor: ...

  7. Contoso 大学 - 10 - 高级 EF 应用场景

    原文 Contoso 大学 - 10 - 高级 EF 应用场景 By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Micros ...

  8. Contoso 大学 - 7 – 处理并发

    原文 Contoso 大学 - 7 – 处理并发 By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Microsoft's W ...

  9. Contoso 大学 - 4 - 创建更加复杂的数据模型

    原文 Contoso 大学 - 4 - 创建更加复杂的数据模型 原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using- ...

随机推荐

  1. windows服务控制类

    /// <summary> /// 服务调用控制 /// </summary> public class WinServiceController { /// <summ ...

  2. oracle连接由于防火墙设置导致超时的问题

        当应用程序使用数据库连接池进行数据连接时,防火墙的设置有可能会导致连接出现超时或者被重置的问题.当从数据库读数据的时候 有可能会 Connection timed out, 这是由于应用会缓存 ...

  3. 使用Unity制作游戏关卡的教程(一)

    转自: http://gamerboom.com/archives/74131 作者:Matthias Zarzecki 我正在制作<Looking For Group – The Fork O ...

  4. SAP BW 例程(Routine)【开始例程、关键值或特性的例程、结束例程】

    定义 可以使用例程定义关键值或特性的复杂的转换规则. 例程是本地 ABAP 类,它们包括预定义的定义和实施范围.进站和出站参数的 TYPES及方法签名都存储在定义范围中.实际例程创建于实施范围中.使用 ...

  5. mongo批量更新

    update的如果要批量更新是无能为力的,如果有多条匹配的结果,但结果是只能更新一条. 用bulk来进行处理 var bulk = db.HIS_ALARM.initializeUnorderedBu ...

  6. Effective C++ Item 29 为”异常安全”而努力是值得的

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:异常安全函数即使发生异常也不会泄漏资源或同意不论什么数据结构败坏.这种函数区分为三种 ...

  7. WWDC2015—图解

       

  8. 日志分析(三) kibana数据展示

    kibana最新版本支持自定义数据大盘了,今天试用了下,感觉非常棒.上图: 支持多维度定义视图,然后视图里面可以依据粗粒度维度进行图表归类.总之,比旧版本强大太多了.后面试试能否扩展加入耦合一定业务数 ...

  9. 十六款值得关注的NoSQL与NewSQL数据库--转载

    原文地址:http://tech.it168.com/a2014/0929/1670/000001670840_all.shtml [IT168 评论]传统关系型数据库在诞生之时并未考虑到如今如火如荼 ...

  10. 架构师书单 2nd Edition--转载

    作者:江南白衣,原文出处: http://blog.csdn.net/calvinxiu/archive/2007/03/06/1522032.aspx,转载请保留. 为了2007年的目标,列了下面待 ...