MVC5 Entity Framework学习之读取相关数据

前一篇文章中完成了School 数据模型,接下来你将学习如何读取和显示相关的数据——这里指Entity Framework加载至导航属性中的数据。

下图是完成后的效果图

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

Entity Framework可以通过多种方法向实体的导航属性中加载数据

  • 延迟加载(Lazy loading) 当实体第一次被读取时,相关数据并不会被检索。但是,当你第一次访问导航属性时,该导航属性所需的数据会自动加载。这是向数据库发送多个查询语句的结果——一次是读取实体本身,接着是每次与被检索的实体相关的数据。DbContext类默认是启动延迟加载的。

  • 预先加载(Eager Loading) 当实体被读取的同时加载与该实体相关的数据。这通常是在单个连接查询中检索出所有所需要的数据,你可以使用Include方法来指定是否使用预先加载。

  • 显式加载(Explicit Loading) 与延迟加载类似,但需要在代码中显示的指明要检索的数据。当你访问一个导航属性时,它并不会自动加载,你需要获得实体的对象状态管理器条目并调用集合的Collection.Load方法或含有单个属性的Reference.Load方法来手动加载相关数据。(在下面的例子中,如果你希望加载Administrator导航属性,你需要将 Collection(x => x.Courses)替换为Reference(x => x.Administrator))通常你应该在禁用延迟加载的情况下使用显示加载。

因为延迟加载和显式加载都不立即检索属性的值,所以它们也被称为延时加载(deferred loading.)。

性能考量

如果你知道你需要为每一个被检索的实体加载相关数据,预先加载通常具有最佳性能,因为单个查询通常比为每一个实体分别进行查询更有效率。例如在上面的例子中,假设每个department 有十个相关的course,预先加载只需要单个连接查询一次往返数据库就可以检索出所有数据,而延迟加载和显式加载都需要11次查询11次往返数据库才能得到同样的结果。在高延迟的情况下,额外的往返对性能是十分不利的。

另一方面,在某些情况下延迟加载具有更高的效率。预先加载可能会生成SQL Server不能有效处理的复杂的连接查询。或者如果你访问的是你正在处理的实体的集合或子集的导航属性,延迟加载会更有效,因为预先加载会检索那些你并不需要的数据。如果性能是至关重要的,那么你最好测试这两种方法以便选择执行效率更好的那一种。

延迟加载会屏蔽那些导致性能问题的代码。例如,那些没有指定预先或显式加载但是处理实体高并发时在每次迭代中都使用了多个导航属性的代码,其执行效率可能会很低(因为会有大量数据库往返)。一个在开发环境下使用On-Premise SQL server表现良好的应用程序可能会在部署到Windows Azure SQL数据库时由于增加了延迟并使用延迟加载而可能导致性能问题。你应该使用真实的测试负载来分析数据库查询以便决定是否使用延迟加载。

在序列化之前禁用延迟加载

如果你在序列化期间启用了延迟加载,那么你将查询到预期多得多的数据。序列化通常会访问实例的每个属性,而属性访问触发延迟加载,并且这些延迟加载的实体会被序列化,然后序列化过程会访问延迟加载的实体的每一个属性,这可能会导致更多的延迟加载和序列化。为了防止这种失控的连锁反应,你需要在序列化实体之前禁用延迟加载。

通过使用Entity Framework的代理类,序列化同样也是横复杂的。

避免序列化问题的一种方法是序列化数据传输对象(DTO)而不是实体对象。

如果你没有使用DTOs,你可以禁用延迟加载并通过使用禁用代理创建来避免代理问题。

下面是一些别的禁用延迟加载的方法:

  • 对于特定的导航属性,在声明时省略virtual关键字
  • 对于所有的导航属性,将LazyLoadingEnabled设置为false,将下面的代码放在上下文类的构造函数中
    this.Configuration.LazyLoadingEnabled = false;

创建Courses页面并显示Department 名称

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>

这里对框架代码进行了如下更改:

  • 将标题从Index改为Course
  • 添加了用来显示CourseID属性值的Number列,默认情况下,框架不会生成显示主键的代码,因为通常它们对最终用户是没有意义的。但是在本例中,你希望将其显示出来
  • 将Department 列移动到右侧显示,并修改其标题。框架会从Department实体中正确的选择其Name属性并显示,但是在Course 页面中,列标题应该显示为Department而不是Name

注意对于Department列,框架代码显示了加载至Department 导航属性中的Department 实体的Name属性。

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

运行项目,选择Courses选项卡,查看数据

创建Instructors页面来显示 Courses 和Enrollments

本节中你将会为Instructor实体创建控制器和试图以便显示Instructors

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

  • Instructor列表显示了OfficeAssignment实体中的相关数据,Instructor和OfficeAssignment实体之间是一对一或零的关系,你可以对OfficeAssignment实体使用预先加载。如前所述,当你需要检索主表中所有行的相关数据时,预先加载更有效率。在本例中,你希望显示所有Instructor的office分配情况。
  • 当用户选择一名Instructor时,相关的Course实体也会被显示出来,Instructor和Course实体之间是多对多的关系。你可以对Course实体和相关的Department实体使用预先加载。在本例中,延迟加载可能更有效率,因为你只需要那些已选择的Instructor的Course信息。但是在本例中,仅演示如何对导航属性实体的导航属性使用预先加载。
  • 当用户选择一门Course时,相关的Enrollments 实体中的数据被显示,Course和Enrollment实体是一对多的关系。你会对Enrollment实体和相关的Student实体使用显式加载(显示加载不是必须的,因为已经启用了延迟加载,这里仅作演示)。

为Instructor Index视图创建视图模型

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

创建Instructor 控制器和视图

使用 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;
    }

修改Instructor Index视图

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

对代码所做的更改:

  • 更改视图模型类为InstructorIndexData
  • 更改标题为Instructors
  • 添加Office列,以便在item.OfficeAssignent不为空时显示item.OfficeAssignment.Location(因为它们是一对零或一的关系,可能不存在相关的OfficeAssignment实体)
    <td>
        @if (item.OfficeAssignment != null)
        {
            @item.OfficeAssignment.Location
        }
    </td> 
  • 为被选中的instructor的tr元素动态添加class="success"样式,通过使用Bootstrap来为被选中的行设置背景颜色
    string selectedRow = "";
    if (item.InstructorID == ViewBag.InstructorID)
    {
        selectedRow = "success";
    }
    <tr class="@selectedRow" valign="top"> 
  • 添加一个新的ActionLink,可以 向Index方法发送所选中的instructor ID

运行项目,选择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

时间: 2024-10-12 13:14:26

MVC5 Entity Framework学习之读取相关数据的相关文章

MVC5 Entity Framework学习之更新相关数据

在上篇文章中学习了如何在页面中显示相关数据,本节中将学习如何对相关数据进行更新.对于大多数实体关系,可以通过更新外键或导航属性来更新数据,对于多对多关系,Entity Framework不会直接公开连接表,所以你需要通过相应的导航属性来添加和移除实体. 先看完成后的效果图 为Courses自定义Create 和Edit 页面 当一个新的course实体被创建时,该实体必须关联到一个已存在的department.要做到这一点,生成的框架代码应该要包括控制器方法和用于选择department的下列列

MVC5 Entity Framework学习

MVC5 Entity Framework学习(1):创建Entity Framework数据模型 MVC5 Entity Framework学习(2):实现基本的CRUD功能 MVC5 Entity Framework学习(3):添加排序.筛选和分页功能 MVC5 Entity Framework学习(4):弹性连接和命令拦截 MVC5 Entity Framework学习(5):Code First迁移和部署 MVC5 Entity Framework学习(6):创建复杂的数据模型 MVC5

MVC5 Entity Framework学习之实现主要的CRUD功能

在上一篇文章中,我们使用Entity Framework 和SQL Server LocalDB创建了一个MVC应用程序,并使用它来存储和显示数据.在这篇文章中,你将对由 MVC框架自己主动创建的CRUD(create, read, update, delete)代码进行改动. 注意:通常我们在控制器和数据訪问层之间创建一个抽象层来实现仓储模式.为了将注意力聚焦在怎样使用实体框架上.这里暂没有使用仓储模式. 在本篇文章中,要创建的web页面: watermark/2/text/aHR0cDovL

MVC5 Entity Framework学习之异步和存储过程

在之前的文章中,你已经学习了如何使用同步编程模型来读取和更新数据,在本节中你将学习如何实现异步编程模型.异步可以使应用程序执行更有效率,因为它可以更有效的使用服务器资源. 同样在本节中你还将学习如何针对实体的insert, update, 和delete操作使用存储过程. 最后将应用程序部署到 Windows Azure. 下面是完成后的页面 为什么要使用异步代码 一个web服务器的可用线程是有限的,在高负载情况下,所有的可用线程可能都在被使用.当出现这种情况时,服务器将无法处理新的请求,直到有

MVC5 Entity Framework学习之处理并发

之前你已经学习了如何更新数据,那么在本节你将学习如何在当多个用户在同一时间更新同一实体时处理冲突. 修改与Department实体相关的那些页面以便它们能够i处理并发错误.下面的截图是Index 和Delete页面,以及当出现并发冲突时的错误消息. 并发冲突 当一个用户对实体的数据进行编辑,然后另一个用户在前一个用户将更改写入到数据库之前更新同一实体的数据时将发生并发冲突.如果你没有启用冲突检测,那么最后一次对数据库的更新将会覆盖其他用户对数据库所做的更改.在大部分应用程序中,这种风险是可以接受

MVC5 Entity Framework学习之Code First迁移和部署

到目前为止,应用程序一直在本地IIS Express 上运行.为了让其他人能够通过互联网访问你的应用程序,你需要将它部署到WEB服务器. 本文章包含以下内容: 启用Code First迁移,迁移功能能够让你不必重建数据库就可以更改数据模型并将其部署到生产环境. 将应用程序部署到Windows Azure(可选) 1.启用Code First迁移 当你在开发应用程序时,你会对数据模型进行频繁的更改,随着每一次的更改,数据模型与数据库架构将不再一致.你已经对Entity Framework进行了配置

MVC5 Entity Framework学习之弹性连接和命令拦截

到目前为止,应用程序一直在本地IIS Express上运行.如果你想让别人通过互联网访问你的应用程序,你必须将它部署到WEB服务器同时将数据库部署到数据库服务器 本篇文章中将教你如何使用在将你的应用程序部署到云环境时的Entity Framework 6的非常有价值的两个特性:弹性连接(瞬时错误的自动重试)和命令拦截(捕获所有发送到数据库的SQL查询语句并记录至日志中). 1.启用弹性连接 当你将应用程序部署到Windows Azure时,相应的数据库部也应被部署到Windows Azure S

MVC5 Entity Framework学习之Entity Framework高级功能

在之前的文章中,你已经学习了如何实现每个层次结构一个表继承.本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时可以利用的高级功能. 在本节中你将重用之前已经创建的页面,接下来你需要新建一个页面并使用原始SQL来批量更新数据库中所有Course的学分. 在Department Edit页面中添加新的验证逻辑并使用非跟踪查询. 执行原始SQL查询 Entity FrameworkCode First API包含有可以让你直接向数据库发送SQ

MVC5 Entity Framework学习之Entity Framework高级功能(转)

在之前的文章中,你已经学习了如何实现每个层次结构一个表继承.本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时可以利用的高级功能. 在本节中你将重用之前已经创建的页面,接下来你需要新建一个页面并使用原始SQL来批量更新数据库中所有Course的学分. 在Department Edit页面中添加新的验证逻辑并使用非跟踪查询. 执行原始SQL查询 Entity FrameworkCode First API包含有可以让你直接向数据库发送SQ