前一篇文章中完成了School 数据模型,接下来你将学习如何读取和显示相关的数据——这里指Entity Framework加载至导航属性中的数据。
下图是完成后的效果图
Entity Framework可以通过多种方法向实体的导航属性中加载数据
因为延迟加载和显式加载都不立即检索属性的值,所以它们也被称为延时加载(deferred loading.)。
如果你知道你需要为每一个被检索的实体加载相关数据,预先加载通常具有最佳性能,因为单个查询通常比为每一个实体分别进行查询更有效率。例如在上面的例子中,假设每个department 有十个相关的course,预先加载只需要单个连接查询一次往返数据库就可以检索出所有数据,而延迟加载和显式加载都需要11次查询11次往返数据库才能得到同样的结果。在高延迟的情况下,额外的往返对性能是十分不利的。
另一方面,在某些情况下延迟加载具有更高的效率。预先加载可能会生成SQL Server不能有效处理的复杂的连接查询。或者如果你访问的是你正在处理的实体的集合或子集的导航属性,延迟加载会更有效,因为预先加载会检索那些你并不需要的数据。如果性能是至关重要的,那么你最好测试这两种方法以便选择执行效率更好的那一种。
延迟加载会屏蔽那些导致性能问题的代码。例如,那些没有指定预先或显式加载但是处理实体高并发时在每次迭代中都使用了多个导航属性的代码,其执行效率可能会很低(因为会有大量数据库往返)。一个在开发环境下使用On-Premise SQL server表现良好的应用程序可能会在部署到Windows Azure SQL数据库时由于增加了延迟并使用延迟加载而可能导致性能问题。你应该使用真实的测试负载来分析数据库查询以便决定是否使用延迟加载。
如果你在序列化期间启用了延迟加载,那么你将查询到预期多得多的数据。序列化通常会访问实例的每个属性,而属性访问触发延迟加载,并且这些延迟加载的实体会被序列化,然后序列化过程会访问延迟加载的实体的每一个属性,这可能会导致更多的延迟加载和序列化。为了防止这种失控的连锁反应,你需要在序列化实体之前禁用延迟加载。
通过使用Entity Framework的代理类,序列化同样也是横复杂的。
避免序列化问题的一种方法是序列化数据传输对象(DTO)而不是实体对象。
如果你没有使用DTOs,你可以禁用延迟加载并通过使用禁用代理创建来避免代理问题。
下面是一些别的禁用延迟加载的方法:
this.Configuration.LazyLoadingEnabled = false;
Course实体包含了一个导航属性,该导航属性包含有Department实体,如果要在course列表中显示已分配的department名称,你需要获得Course.Department导航属性中的Department实体中的Name属性。
使用"MVC 5 Controller with views, using Entity Framework"框架为Course实体类型新建一个名为CourseController的控制器,就像之前为Student创建控制器那样
打开Controllers\CourseController.cs,查看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>这里对框架代码进行了如下更改:
注意对于Department列,框架代码显示了加载至Department 导航属性中的Department 实体的Name属性。
<td> @Html.DisplayFor(modelItem => item.Department.Name) </td>运行项目,选择Courses选项卡,查看数据
本节中你将会为Instructor实体创建控制器和试图以便显示Instructors
Instructors 页面显示了三个不同的表格,你会创建一个包含三个属性的视图模型,每个属性含有一个表格所需的数据。
打开ViewModels文件夹,创建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; } } }
使用 EF read/write actions框架创建InstructorController 控制器
打开Controllers\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)用来提供所选instructor 和course的ID值,并传递给视图所需要的数据。参数是由页面上的Select 链接提供的。
上面的代码首先创建了一个视图模型的实例并将instructors列表放入其中,该代码指定对于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实体,并为每个被加载的Course实体预先加载了Course.Department导航属性。
.Include(i => i.Courses.Select(c => c.Department))如前所述,预先加载不是必须的,但是在这里使用是为了提高程序性能。由于视图总是需要OfficeAssgnment实体,因此在同一个查询中检索它们是更有效率的。当一个instructor 在页面中被选中时,Course实体是必需的,所以仅在页面中经常显示被选择的course 时,预先加载比延迟加载更有效率。
如果一个instructor ID被选中,被选中的instructor 会从视图模型的instructor 列表中来检索,然后视图模型的Courses实体会通过instructor 的Course导航属性的Courses属性来加载。
if (id != null) { ViewBag.InstructorID = id.Value; viewModel.Courses = viewModel.Instructors.Where(i => i.ID == id.Value).Single().Courses; }Where方法会返回一个集合,但是在本例中,该方法通过传递的参数仅返回了一个Instructor 实体。Single方法可以将集合转换为一个Instructor 实体,使你能够访问该实体的Courses属性。
当你知道集合只含有一个元素时,你可以使用集合的Single方法。当集合为空或者含有多个元素时,Single方法会抛出一个异常。另一中选择是使用SingleOrDefault,如果集合为空,该方法会会返回一个默认值。但在本例中使用SingleOrDefault仍会引发异常(在null引用中查询Courses属性),但异常信息并没有明确指出引起问题的原因。当调用Single方法时,你也可以直接将其当做Where条件而不是分别调用Where及Single方法:
.Single(i => i.ID == id.Value)而不是:
.Where(I => i.ID == id.Value).Single()
接下来,如果选择了一门course,该course会从视图模型中的course列表中检索。然后视图模型的Enrollments 实体会通过Course的Enrollments 导航属性的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 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.InstructorID == ViewBag.InstructorID) { selectedRow = "success"; } <tr class="@selectedRow" valign="top">
运行项目,选择Instructors选项卡,页面上显示了相关OfficeAssignment实体的Location属性值,如果OfficeAssignment实体为空,则什么也不显示。
打开 Views\Instructor\Index.cshtml,在table元素结束标记后面添加如下代码,用来显示被选中instructor的Course列表
@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> }上面的代码通过读取视图模型中的Course属性来显示course列表,同时它还提供了一个Select链接用来被选中course的ID传递给Index方法。
运行项目,选择一个instructor,你可以看到页面中显示了分配给被选中instructor的course和course的Department
在你刚才添加代码的后面再次添加如下代码,用来显示那些选修被选中course的student列表
@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属性来显示那些选修被选中course的student列表
运行项目,选择一个instructor,在选择一门course,可以看到页面中显示了选修该course的student列表
打开nstructorController.cs,查看Index方法中是如何取得被选中course的Enrollment列表的
if (courseID != null) { ViewBag.CourseID = courseID.Value; viewModel.Enrollments = viewModel.Courses.Where( x => x.CourseID == courseID).Single().Enrollments; }
当检索instructor列表时,你为Courses导航属性和每个Course的Department属性指定了预先加载,然后将Courses集合传递到视图模型中,接下来你就可以访问该集合中每个实体的Enrollments导航属性。由于你没有为Course.Enrollments导航属性指定预先加载,所以在页面中呈现该属性中的数据时使用的是延迟加载。
如果你禁用了延迟加载而没有修改任何代码,那么Enrollments 属性值将会是null而不管course实际含有多少enrollment。在这种情况下,要加载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.ID == id.Value).Single().Courses; } if (courseID != null) { ViewBag.CourseID = courseID.Value; // Lazy loading //viewModel.Enrollments = viewModel.Courses.Where( // x => x.CourseID == courseID).Single().Enrollments; // Explicit loading 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实体后显示加载course的Enrollments导航属性
db.Entry(selectedCourse).Collection(x => x.Enrollments).Load();然后显示加载与每个Enrollment实体相关的Student实体
db.Entry(enrollment).Reference(x => x.Student).Load();注意你使用了Collection 属性来加载集合属性,但是对于仅含有一个实体的属性,你应该使用Reference 属性。
运行项目,你会发现页面呈现数据并没有什么不一样的地方,但是其实我们已经更改了数据的检索方式。
原文:Reading Related Data with the Entity Framework in an ASP.NET MVC Application
欢迎转载,请注明文章出处:http://blog.csdn.net/johnsonblog/article/details/39136963
还大家一个健康的网络环境,从你我做起
THE END