前面创建了学校数据模型。 本节将读取并显示相关数据 - 即 Entity Framework 加载到导航属性中的数据。

相关数据的预先加载、显式加载和延迟加载

对象关系映射 (ORM) 框架(如 Entity Framework)可通过多种方式将相关数据加载到实体的导航属性中:

  • 预先加载。 读取该实体时,会同时检索相关数据。 此时通常会出现单一联接查询,检索所有必需数据。可使用 Include 和 ThenInclude 方法指定 Entity Framework Core 中的预先加载。

可在单独查询中检索一些数据,EF 会“修正”导航属性。 也就是说,EF 会自动添加单独检索的实体,将其添加到之前检索的实体的导航属性中所属的位置。 对于检索相关数据的查询,可使用 Load 方法,而不采用返回列表或对象的方法,如 ToList 或 Single

  • 显式加载。 首次读取实体时,不检索相关数据。 如有需要,可编写检索相关数据的代码。 就像使用单独查询进行预先加载一样,显式加载时会向数据库发送多个查询。 二者的区别在于,代码通过显式加载指定要加载的导航属性。 在 Entity Framework Core 1.1 中,可使用 Load 方法执行显式加载。 例如:

  • 延迟加载。 首次读取实体时,不检索相关数据。 然而,首次尝试访问导航属性时,会自动检索导航属性所需的数据。 每次首次尝试从导航属性获取数据时,都向数据库发送查询。 Entity Framework Core 1.0 不支持延迟加载。

性能注意事项

如果知道自己需要每个检索的实体的相关数据,选择预先加载可获得最佳性能,因为相比每个检索的实体的单独查询,发送到数据库的单个查询更加有效。 例如,假设每个系有十个相关课程。 预先加载所有相关数据时,只会进行单一(联接)查询,往返数据库一次。 单独查询每个系的课程时,会往返数据库十一次。 延迟较高时,额外往返数据库对性能尤为不利。

另一方面,在某些情况下,单独查询会更加高效。 在一个查询中预先加载所有相关数据时,可能会生成一个非常复杂的联接,SQL Server 无法有效处理该联接。 或者,如果你正在处理一组实体且只需访问其子集的导航属性,那么采用单独查询可获得更佳性能,因为预先加载所有数据后,会检索不需要的数据。 如果看重性能,那么最好测试两种方式的性能,以便做出最佳选择。

创建显示院系名称的“课程”页

Course 实体包括导航属性,其中包含分配有课程的系的 Department 实体。 若要在课程列表中显示接受分配的系的名称,需从位于 Course.Department 导航属性中的 Department 实体获取 Name 属性。

使用与带视图的 MVC 控制器相同的选项,及之前用于学生控制器的 Entity Framework 基架为 Course 实体类型创建名为 CoursesController 的控制器,如下图所示:

打开 CoursesController.cs 并检查 Index 方法。 自动基架使用 Include 方法为 Department 导航属性指定了预先加载。

将 Index 方法替换为以下代码,该代码为返回 Course 实体(是 courses 而不是 schoolContext)的 IQueryable 赋予了更合适的名称:

public async Task<IActionResult> Index()
{var courses = _context.Courses.Include(c => c.Department).AsNoTracking();return View(await courses.ToListAsync());
}

在 Views/Courses/Index.cshtml 中,将模板代码替换为以下代码。 突出显示所作更改:

@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>

已对基架代码进行了如下更改:

  • 将标题从“索引”更改为“课程”。

  • 添加了显示 CourseID 属性值的“数字”列。 默认情况下,不针对主键进行架构,因为对最终用户而言,它们通常没有意义。 但在这种情况下主键是有意义的,而你需要将其呈现出来。

  • 更改“院系”列,显示院系名称。 该代码显示已加载到 Department 导航属性中的 Department 实体的 Name 属性:

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

运行应用并选择“课程”选项卡,查看包含系名称的列表。

创建显示“课程”和“注册”的“讲师”页

本节将为 Instructor 实体创建一个控制器和视图,从而显示“讲师”页:

该页面通过以下方式读取和显示相关数据:

  • 讲师列表显示 OfficeAssignment 实体的相关数据。 Instructor 与 OfficeAssignment 实体间存在一对零或一的关系。 将预先加载 OfficeAssignment 实体。 如前所述,需要主表所有检索行的相关数据时,预先加载通常更有效。 在这种情况下,你希望显示所有显示的讲师的办公室分配情况。

  • 用户选择一名讲师时,显示相关 Course 实体。 Instructor 和 Course 实体是多对多关系。 预先加载 Course 实体及其相关 Department 实体。 在这种情况下,单独查询可能更有效,因为仅需显示所选讲师的课程。 但此示例显示的是如何在本身就位于导航属性内的实体中预先加载导航属性。

  • 用户选择一门课程时,会显示 Enrollment 实体集的相关数据。 Course 和 Enrollment 实体是一对多关系。 单独查询 Enrollment 实体及其相关 Student 实体。

创建“讲师索引”视图的视图模型

“讲师”页显示来自三个不同表格的数据。 因此将创建包含三个属性的视图模型,每个属性都包含一个表的数据。

在 SchoolViewModels 文件夹中,创建 InstructorIndexData.cs,并使用以下代码替换现有代码:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5
 6 namespace ContosoUniversity.Models.SchoolViewModels
 7 {
 8     public class InstructorIndexData
 9     {
10         public IEnumerable<Instructor> Instructors { get; set; }
11         public IEnumerable<Course> Courses { get; set; }
12         public IEnumerable<Enrollment> Enrollments { get; set; }
13     }
14 }

创建讲师控制器和视图

使用 EF 读/写操作创建讲师控制器,如下图所示:

打开 InstructorsController.cs 并为 ViewModels 名称空间添加 using 语句:

using ContosoUniversity.Models.SchoolViewModels;

使用以下代码替换 Index 方法,预先加载相关数据并将其放入视图模型。

 1 public async Task<IActionResult> Index(int? id, int? courseID)
 2 {
 3     var viewModel = new InstructorIndexData();
 4     viewModel.Instructors = await _context.Instructors
 5           .Include(i => i.OfficeAssignment)
 6           .Include(i => i.CourseAssignments)
 7             .ThenInclude(i => i.Course)
 8                 .ThenInclude(i => i.Enrollments)
 9                     .ThenInclude(i => i.Student)
10           .Include(i => i.CourseAssignments)
11             .ThenInclude(i => i.Course)
12                 .ThenInclude(i => i.Department)
13           .AsNoTracking()
14           .OrderBy(i => i.LastName)
15           .ToListAsync();
16
17     if (id != null)
18     {
19         ViewData["InstructorID"] = id.Value;
20         Instructor instructor = viewModel.Instructors.Where(
21             i => i.ID == id.Value).Single();
22         viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
23     }
24
25     if (courseID != null)
26     {
27         ViewData["CourseID"] = courseID.Value;
28         viewModel.Enrollments = viewModel.Courses.Where(
29             x => x.CourseID == courseID).Single().Enrollments;
30     }
31
32     return View(viewModel);
33 }

该方法接受可选路由数据 (id) 和查询字符串参数 (courseID),二者提供所选讲师和课程的 ID 值。 参数由页面上的“选择”超链接提供。

代码先创建一个视图模型实例,并在其中放入讲师列表。 代码指定预先加载 Instructor.OfficeAssignment 和 Instructor.CourseAssignments 导航属性。 在 CourseAssignments 属性中,加载 Course 属性,在其中加载 Enrollments 和 Department 属性,同时在每个 Enrollment 实体中加载 Student 属性。

由于视图始终需要 OfficeAssignment 实体,因此更有效的做法是在同一查询中获取。 在网页中选择讲师后,需要 Course 实体,因此只有在页面频繁显示选中课程时,单个查询才比多个查询更有效。

代码重复 CourseAssignments 和 Course,因为你需要 Course 中的两个属性。 ThenInclude 调用的第一个字符串获取 CourseAssignment.CourseCourse.Enrollments 和 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();

此时,代码中的另一个 ThenInclude 将成为 Student 的导航属性,你不需要该属性。 但调用 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();

选择讲师时,将执行以下代码。 从视图模型中的讲师列表检索所选讲师。 然后,该视图模型的 Courses 属性加载讲师 CourseAssignments 导航属性中的 Course 实体。

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 方法将集合转换为单个 Instructor 实体,让你可以访问该实体的 CourseAssignments 属性。 CourseAssignments属性包含多个 CourseAssignment 实体,而你只需要相关的 Course 实体。

如果知道集合只有一个项,则可在集合上使用 Single 方法。 如果集合为空或包含多个项,Single 方法将引发异常。 还可使用 SingleOrDefault,该方式在集合为空时返回默认值(本例中为 null)。 但在这种情况下,仍会引发异常(尝试在 null 引用上查找 Courses 属性时),并且异常消息不会清楚指出异常原因。 调用 Single 方法时,还可传入 Where 条件,而不是分别调用 Where 方法:

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

而不是:

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

接着,如果选择了课程,则从视图模型中的课程列表中检索所选课程。 然后,视图模型的 Enrollments 属性加载该课程 Enrollments 导航属性中的 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>

已对现有代码进行了如下更改:

  • 将模型类更改为了 InstructorIndexData

  • 将页标题从“索引”更改为了“讲师”。

  • 添加了仅在 item.OfficeAssignment 不为 null 时才显示 item.OfficeAssignment.Location 的“办公室”列。(由于这是一对零或一的关系,因此可能没有相关的 OfficeAssignment 实体。)

@if (item.OfficeAssignment != null)
{@item.OfficeAssignment.Location
}

  • 添加了显示每位讲师所授课程的“课程”列。

  • 添加了向所选讲师的 tr 元素中动态添加 class="success" 的代码。 此时会使用 Bootstrap 类为所选行设置背景色。

string selectedRow = "";
if (item.ID == (int?)ViewData["InstructorID"])
{selectedRow = "success";
}
<tr class="@selectedRow">

  • 紧贴每行其他链接的前端添加了标有 Select 的新超链接,从而使所选讲师 ID 发送到 Index 方法。

<a asp-action="Index" asp-route-id="@item.ID">Select</a> |

运行应用并选择“讲师”选项卡。没有相关 OfficeAssignment 实体时,该页面显示相关 OfficeAssignment 实体的 Location 属性和空表格单元格。

在 Views/Instructors/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>
}

View Code

此代码读取视图模型的 Courses 属性以显示课程列表。 它还提供 Select 超链接,该链接可将所选课程的 ID 发送到 Index 操作方法。

刷新页面并选择讲师。 此时会出现一个网格,其中显示有分配给所选讲师的课程,且还显示有每个课程的分配系的名称。

在刚刚添加的代码块后,添加以下代码。 选择课程后,代码将显示参与课程的学生列表。

@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>
}

View Code

此代码读取视图模型的 Enrollment 属性,从而显示参与课程的学生列表。

再次刷新该页并选择讲师。 然后选择一门课程,查看参与的学生列表及其成绩。

显式加载

在 InstructorsController.cs 中检索讲师列表时,指定了预先加载 CourseAssignments 导航属性。

假设你希望用户在选中讲师和课程时尽量少查看注册情况。 此时建议只在有请求时加载注册数据。 若要查看如何执行显式加载的示例,请使用以下代码替换 Index 方法,这将删除预先加载 Enrollment 并显式加载该属性。 代码所作更改为突出显示状态。

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

新代码将从检索 Instructor 实体的代码中删除注册数据的 ThenInclude 方法调用。 如果选择了讲师和课程,突出显示的代码会检索所选课程的 Enrollment 实体,及每个 Enrollment 的 Student 实体。

运行应用,立即转到“讲师”索引页,尽管已经更改了数据的检索方式,但该页上显示的内容没有任何不同。

总结

本节已学会使用了预先加载和一个查询及多个查询来读取导航属性中的相关数据。

*****************************
*** Keep learning and growing. ***
*****************************

转载于:https://www.cnblogs.com/gangle/p/9227873.html

用ASP.NET Core MVC 和 EF Core 构建Web应用 (六)相关推荐

  1. 用ASP.NET Core MVC 和 EF Core 构建Web应用 (一)

    系统必备 .NET Core 2.0.0 SDK 或更高版本. 已安装 ASP.NET 和 Web 开发工作负载的 Visual Studio 2017 15.3 版或更高版本. 创建Web应用程序 ...

  2. ASP.NET Core MVC 和 EF Core 教程 - 创建、读取、更新和删除

    作者:Tom Dykstra 和 Rick Anderson Contoso 大学示例 web 应用程序演示如何使用 Entity Framework Core 和 Visual Studio 创建 ...

  3. ASP.NET Core MVC with EF Core-迁移

    当你开发一个新的应用程序的时候,你的模型频繁的变化,而每一次的数据模型的改变,将使它与数据库不同步.你通过配置EF Core,使得数据库不存在时创建数据库.每一次改变数据模型(增删改 实体类或者改变D ...

  4. 基于Asp.Net Core Mvc和EntityFramework Core 的实战入门教程系列-2

    来个目录吧: 第一章-入门 第二章- Entity Framework Core Nuget包管理 第三章-创建.修改.删除.查询 第四章-排序.过滤.分页.分组 第五章-迁移,EF Core 的co ...

  5. 用于存储过程的ASP.NET Core Blazor和EF Core原始SQL查询

    目录 介绍 背景 先决条件 使用代码 创建数据库和表 步骤1:创建ASP.NET Core Blazor服务器应用程序 运行测试应用程序 步骤2:安装软件包 连接字符串 步骤3:建立模型类 创建DBC ...

  6. mysql多租户schema复制,Asp.net core下利用EF core实现从数据实现多租户(3): 按Schema分离 附加:EF Migration 操作...

    前言 前段时间写了EF core实现多租户的文章,实现了根据数据库,数据表进行多租户数据隔离. 今天开始写按照Schema分离的文章. 其实还有一种,是通过在数据表内添加一个字段做多租户的,但是这种模 ...

  7. Asp.net core 学习笔记 ( ef core )

    更新: 2019-06-12 不小心踩坑 var adidas = new Supplier { name = "adidas" }; Db.Suppliers.Add(adida ...

  8. ef core mysql 字符集,EF Core 基础知识

    数据库连接字符串 在 ASP.NET Core 添加配置片段: { "ConnectionStrings": { "BloggingDatabase": &qu ...

  9. .NET Core 之 七 EF Core(四)

    一.有了IEnumerable 还要IQueryable干什么 普通集合的版本(IEnumerable)是在内存中过滤(客户端评估),而IQueryable版本则是把查询操作翻译成SQL语句后给数据库 ...

最新文章

  1. 侧边栏qq客服对话显示
  2. 按装oracle后 eclips提示jvm版本太低的问题
  3. Jvisualvm--JAVA性能分析工具
  4. Why IBASE category 03 is filtered out in creation
  5. pg日期转周_postgresql 存储过程函数:时间戳与日期字符串相互转换
  6. 虚数填补了数学的那一个缺口?
  7. 达摩院 2021 十大科技趋势:云原生重塑IT技术体系
  8. 昆明北大附中2021高考成绩查询,北大附中云南实验学校2021年招生代码
  9. 大漠软件c语言教程,大漠万能脚本编辑器无需写代码,截图可以制作脚本附视频教程...
  10. win7网络改局域网计算机名,局域网共享一键修复工具(支持win7) 修复windows7各种共享问题...
  11. 京东联盟api获取数据
  12. VINS-Mono代码解读——视觉跟踪 feature_trackers
  13. Mybatis 特殊符号(大于,小于,不等于)及常用函数总结
  14. directx是什么?
  15. 兔子数列规律怎么讲_神奇兔子数列
  16. Linux基础知识详解
  17. 计算机应用月什么,计算机应用月考试卷
  18. 一个软件网络连接异常_拥有苹果电脑后,最应该预装的7款Mac应用软件
  19. 便携式储能系统---“钱景”无限
  20. 解决OpenSSL 在VC2015下链接报错的问题。

热门文章

  1. 如何长期有效维护客户关系,你真的了解你的客户吗?
  2. 迅雷网心云赚钱宝3代【Pro】8核性能神器,真实收益有多高?
  3. N880E ICS4.0搜索键改锁屏 仅修改一文件的一处
  4. 51单片机(STC89C52)的中断和定时器
  5. 计算机一级电子表格计算公式,计算机一级电子表格(23页)-原创力文档
  6. 百度非企渠道开户怎么玩?
  7. PHP 中 foreach和for循环哪个效率更高
  8. Python实现ALO蚁狮优化算法优化支持向量机回归模型(SVR算法)项目实战
  9. BUUCTF:异性相吸
  10. nodejs MVC框架:Adonisjs框架入门-001概述