MVC 5在Visual Basic的EF 6中 - 阅读相关数据

本教程是第七系列中的第七,它教导您如何使用实体框架构建MVC 5应用程序进行数据访问和Visual Basic。在上一个教程中,您完成学校数据模型。在本教程中,您将读取和显示相关数据 - 即实体框架加载到导航属性的数据。

原辅导系列,由汤姆Dykstra生产 里克安德森 ( @rickandmsft. )使用C#语言编写。我的版本尽可能靠近原件,只改变编码语言。叙述文本从原始文本保持不变,并与Microsoft权限一起使用。

本教程系列教授如何使用实体框架6创建ASP.NET MVC 5应用程序 Visual Studio 2013 Express for web。该系列使用代码第一工作流程。有关如何在代码第一,数据库首先选择的信息的信息,请参阅 实体框架开发工作流程.

教程系列总共包括12个部分。它们旨在顺序顺序排列,因为每个部分都在上一节中赋予的知识。通过这些部分的进步反映在Visual Studio Express for Web项目下载,该部分伴随着具有您通过该系列构建的Web应用程序的每个部分。

下载代码

可用本节的代码 这里 。拯救 。压缩 文件到方便的位置,然后提取内容。确保您安装了一个版本的Visual Studio 2013(Express for Web,Professional,Premium或Ultimate),然后双击 .sln. 文件。项目在IDE中打开后,按 Shift + Ctrl + B 建立解决方案。这将确保从Nuget恢复所有包,并且可能需要一段时间根据您的互联网连接速度。

通过该系列的导航路径如下:

  1. 创建实体框架数据模型
  2. 实施基本的CRUD功能
  3. 排序,过滤和分页
  4. 连接弹性和命令拦截
  5. 代码首次迁移和部署
  6. 创建更复杂的数据模型
  7. 阅读相关数据
  8. 更新相关数据
  9. 异步和存储过程
  10. 处理并发
  11. 实现 - 继承
  12. 高级实体框架方案

懒惰,渴望和明确加载相关数据

实体框架有几种方法可以将相关数据加载到实体的导航属性中:

  • 懒惰的装载。首先读取实体时,未检索相关数据。但是,首次尝试访问导航属性时,将自动检索该导航属性所需的数据。这导致发送到数据库的多个查询 - 一个用于实体本身的查询,并且每次必须检索实体的相关数据时。这  dbcontext.  级别默认启用延迟加载。 

    MVC5与EF6.

  • 渴望加载。当读取实体时,将与其一起检索相关数据。这通常导致单个连接查询检索所需的所有数据。您使用包含方法指定急切加载。

    MVC5与EF6.

  • 显式加载。这类似于延迟加载,除了您显式检索代码中的相关数据;访问导航属性时,它不会自动发生。通过获取实体的对象状态管理器条目,手动加载相关数据并调用 collection.load.load. 收集方法或方法 参考 保持单个实体的属性的方法。在以下示例中,如果您想要加载 行政人员 导航属性,您将更换 集合(函数(x)x.courses)  参考(函数(x)x.administrator)。通常,只有在关闭延迟加载时才会使用显式加载。

    MVC5与EF6.

因为它们没有立即检索属性值,所以延迟加载和显式加载也都被称为 延期装载.

绩效考虑因素

如果您知道您需要检索每个实体的相关数据,则急切加载通常提供最佳性能,因为发送到数据库的单个查询通常比检索到的每个实体的单独查询更有效。例如,在上面的例子中,假设每个部门有十个相关课程。急切加载示例将导致单个(加入)查询和数据库的单个往返。延迟加载和显式加载示例都会导致11个查询和11往返数据库的次级。当延迟高时,额外的往返数据库的额外旅行尤其有害性能。

另一方面,在某些情况下延迟加载更有效。急切加载可能会导致生成非常复杂的连接,SQL Server无法有效地处理。或者如果需要仅访问实体的导航属性仅在您正在处理的一个集合的子集中,延迟加载可能会更好地执行,因为急切加载会检索比您需要的更多数据。如果性能至关重要,最好测试性能,以便成为最佳选择。

延迟加载可以掩码导致性能问题的代码。例如,未指定急切或显式加载的代码,但处理大量实体并在每个迭代中使用多个导航属性可能是非常效率的(因为许多往返数据库)。由于延迟和延迟加载增加,使用On Premise SQL Server在Windows Azure SQL数据库移动到Windows Azure SQL数据库时,在开发中执行良好的应用程序可能具有性能问题。分析具有逼真的测试负载的数据库查询将帮助您确定延迟加载是否合适。有关更多信息,请参阅 脱酥制实体框架策略:加载相关数据 and 使用实体框架将网络延迟降低到SQL Azure.

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

如果在序列化期间启用启用延迟加载,则最终可以查询比您预期的数据明显更多。序列化通常通过在类型的实例上访问每个属性来工作。属性访问触发器延迟加载,并且序列化的延迟加载实体。然后,序列化进程访问懒惰加载实体的每个属性,可能导致更加延迟的加载和序列化。为了防止这种滞后的链式反应,在序列化实体之前关闭延迟加载。

序列化也可以通过实体框架使用的代理类别复杂,如下所述 高级方案教程.

避免序列化问题的一种方法是序列化数据传输对象(DTO)而不是实体对象,如图所示 使用Web API与实体框架 tutorial.

如果您不使用DTO,您可以禁用延迟加载并避免代理问题 禁用代理创建.

这是其他一些 禁用延迟装载的方法:

  • 有关特定导航属性,请省略声明属性时的虚拟关键字。
  • 适用于所有导航属性,设置 LazyloadingEnabled. to  错误的 ,将以下代码放在上下文类的构造函数中:
    Configuration.LazyLoadingEnabled = False

创建显示部门名称的课程页面

这   课程  实体包括包含该的导航属性  部门  课程分配的部门的实体。要在课程列表列表中显示分配部门的名称,您需要从课程中的部门实体中获取名称属性.Department导航属性。

创建名为的控制器  课程 Controller. for the  课程  实体类型,使用相同的选项 MVC 5控制器具有视图,使用实体框架 您之前为学生控制器做过的脚手架,如下图所示:

MVC5与EF6.

打开  控制器\ coursecontroller.vb. 并查看索引方法:

 

Function  指数 () As ActionResult
    Dim courses = db.Courses.Include(Function(c) c.Department)
    Return View(courses.ToList())
End Function

自动脚手架通过使用包含方法指定了部门导航属性的急切加载。

打开  视图\ index \ index.vbhtml 并使用以下代码替换模板代码。更改突出显示:

@ModelType IEnumerable(Of ContosoUniversity.Models. 课程 )
@Code
    ViewBag.Title = "Courses"
End Code

<h2> 课程 </h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(Function(model) model.CourseID)
        </th>
        <th>
            @Html.DisplayNameFor(Function(model) model.Title)
        </th>
        <th>
            @Html.DisplayNameFor(Function(model) model.Credits)
        </th>
        <th>
             部门 
        </th>
        <th></th>
    </tr>

@For Each item Model
    @<tr>
        <td>
            @Html.DisplayFor(Function(modelItem) item.CourseID)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) item.Title)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) item.Credits)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) item.Department.Name)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", New With {.id = item.CourseID }) |
            @Html.ActionLink("Details", "Details", New With {.id = item.CourseID }) |
            @Html.ActionLink("Delete", "Delete", New With {.id = item.CourseID })
        </td>
    </tr>
Next

</table>

您对脚手架代码进行了以下更改:

  • 改变了标题  指数  to  课程 .
  • 添加了A.   数字  显示的列  课程  适当的价值。默认情况下,主键不是脚手架,因为通常它们是毫无意义的最终用户。但是,在这种情况下,主键是有意义的,您想要显示它。
  • 搬了   部门  右侧的列并改变了标题。脚手架正确选择了显示  名称  property from the  部门  实体,但这里在课程页面中应该是  部门  rather than  名称 .

请注意,对于部门列,脚手架代码显示  名称  property of the  部门  载入的实体  部门  navigation property:

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

运行页面(选择  课程  在Contoso大学主页上的选项卡)查看有部门名称的列表。

MVC5与EF6.

创建一个显示课程和注册的教练页面

在本节中,您将创建一个控制器并查看  讲师  实体才能显示教师页面:

MVC5与EF6. 

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

  • 教师列表显示相关数据 甲式甲型法官 entity. The  讲师  and 甲式甲型法官 实体处于一对零或一个关系。您将使用急切的加载为官方社会实体。如前所述,当您需要为主表的所有检索行的相关数据时,急切加载通常更有效。在这种情况下,您要为所有显示的教练展示Office分配。
  • 当用户选择教师,相关  课程  显示实体。这  讲师  课程实体处于多对多的关系中。你将使用急切装载  课程  实体及其相关部门实体。在这种情况下,延迟加载可能会更有效率,因为您只需要选定的教练的课程。但是,此示例显示了如何在导航属性中使用急切加载在本身的实体中的导航属性。
  • 当用户选择课程时,相关数据 招收 显示实体集。这  课程  and 注册  实体处于一对多关系。您将添加显式加载  注册  实体及其相关的  学生  实体。 (由于启用了延迟加载,因此不需要显式加载,但这显示了如何进行显式加载。)

为讲师索引视图创建一个视图模型

教师页面显示三个不同的表。因此,您将创建一个包含三个属性的视图模型,每个属性都包含其中一个表的数据。

在里面  viewmodels. folder, create ChanneriandexData.vb. 使用以下代码替换现有代码:

 进口  ContosoUniversity.Models
 名称 space viewmodels.
    Public Class  讲师 IndexData
        Public Property 教师 As IEnumerable(Of  讲师 )
        Public Property  课程  As IEnumerable(Of  课程 )
        Public Property 招收 As IEnumerable(Of  注册 )
    End Class
End Namespace

创建教师控制器和视图

使用EF读/写操作创建一个教练控制器控制器,如下图所示:

MVC5与EF6. 

打开  控制器\ ContructorController.vb. and add an  进口  statement for the viewmodels. namespace:

 进口  ContosoUniversity.ViewModels
脚手架代码  指数  方法仅针对急切加载 甲式甲型法官 navigation property:

 

Function  指数 () As ActionResult
    Dim instructors = db.Instructors.Include(Function(i) i.OfficeAssignment)
    Return View(instructors.ToList())
End Function

更换  指数  具有以下代码的方法来加载其他相关数据并将其放在视图模型中:

Function  指数 (ByVal   ID   As Integer?, ByVal  课程  As Integer?) As ActionResult
    Dim viewModel = New  讲师 IndexData()
    viewModel.Instructors = db.Instructors _
        .Include(Function(i) i.OfficeAssignment) _
        .Include(Function(i) i.Courses.Select(Function(c) c.Department)) _
        .OrderBy(Function(i) i.LastName)
    If   ID  .HasValue  这 n
        ViewBag.InstructorID = id.Value
        viewModel.Courses = viewModel.Instructors.Where(Function(i) i.ID = id.Value).Single().Courses
    End If
    If  课程 .HasValue  这 n
        ViewBag.CourseID = courseID.Value
        viewModel.Enrollments = viewModel.Courses.Where(Function(x) x.CourseID = courseID).Single().Enrollments
    End If
    Return View(viewModel)
End Function

该方法接受可选的路由数据( ID )和查询字符串参数( 课程 )提供所选教师和所选课程的ID值,并将所有所需数据传递给视图。参数由  选择  页面上的超链接。

代码开始通过创建视图模型的实例并将其放入导师列表中。代码指定急切加载 教练.OfficeAssign. and the 教师.Courses. navigation property.

Dim viewModel = New  讲师 IndexData()
viewModel.Instructors = db.Instructors _
    .Include(Function(i) i.OfficeAssignment) _
    .Include(Function(i) i.Courses.Select(Function(c) c.Department)) _
    .OrderBy(Function(i) i.LastName)
    

第二   包括  方法加载课程,以及加载的每门课程它会急于加载 课程 导航属性。

.Include(Function(i) i.Courses.Select(Function(c) c.Department))

如前所述,不需要急切加载,但是可以提高性能。由于观点总是需要 甲式甲型法官 实体,在同一查询中获取它更有效。当在网页中选择了讲师时,课程实体是必需的,因此急切加载才能延长延迟加载,只有在选择的课程更常见的情况下更频繁地显示页面。

如果选择了讲师ID,则从视图模型中的教师列表中检索所选教学。视图模型  课程  然后加载属性  课程  来自该教练的实体  课程 navigation property.

If   ID  .HasValue  这 n
    ViewBag.InstructorID = id.Value
    viewModel.Courses = viewModel.Instructors.Where(Function(i) i.ID = id.Value).Single().Courses
End If

这   在哪里  方法返回一个集合,但在这种情况下,将传递给该方法的条件仅导致单个 讲师  返回实体。这  单身的  方法将集合转换为单个  讲师  实体,可以让您访问该实体  课程  property.

你用了  单身的  在收集时,在收集时的方法只有一个项目只有一个项目。如果将收集传递给它是空的,则单个方法会抛出异常,或者如果有多个项目。另一种选择是 singledefault.,返回默认值( 没有什么  在这种情况下)如果收集是空的。但是,在这种情况下,仍然会导致异常(从尝试在null参考上找到课程属性),并且异常消息更明确表示问题的原因。当你打电话的时候  单身的  方法,您也可以通过  在哪里  条件而不是呼叫  在哪里  method separately:

 

viewModel.Courses = viewModel.Instructors.Single(Function(i) i.ID = id.Value).Courses

代替:

viewModel.Instructors.Where(Function(i) i.ID = id.Value).Single().Courses

接下来,如果选择了课程,则从视图模型中的课程列表中检索所选课程。然后是视图模型 招收 属性加载  注册  来自该课程的实体 招收 navigation property.

If  课程 .HasValue  这 n
    ViewBag.CourseID = courseID.Value
    viewModel.Enrollments = viewModel.Courses.Where(Function(x) x.CourseID = courseID).Single().Enrollments
End If

修改教师索引视图

在  视图\ extender \ index.vbhtml,用以下代码替换模板代码。更改突出显示:

@ModelType ContosoUniversity.ViewModels. 讲师 IndexData
@Code
    ViewBag.Title = "Instructors"
End Code

<h2>教师</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>
             办公室 
        </th>
        <th></th>
    </tr>

@For Each item Model.Instructors
    Dim selectedRow = String.Empty
    If item.ID = ViewBag.InstructorID  这 n selectedRow = "success"
    @<tr class="@selectedRow">
        <td>
            @Html.DisplayFor(Function(modelItem) item.LastName)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) item.FirstMidName)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) item.HireDate)
        </td>
        <td>
            @If item.OfficeAssignment IsNot Nothing Then
                @item.OfficeAssignment.Location 
            End If
        </td>
        <td>
            @Html.ActionLink("Select", "Index", New With {.id = item.ID}) |
            @Html.ActionLink("Edit", "Edit", New With {.id = item.ID }) |
            @Html.ActionLink("Details", "Details", New With {.id = item.ID }) |
            @Html.ActionLink("Delete", "Delete", New With {.id = item.ID })
        </td>
    </tr>
Next

</table>

您对现有代码进行了以下更改:

  • 将Model类更改为ChircorIndexData。
  • 更改了页面标题  指数  to 教师.
  • 添加了一个   办公室  显示item.officeassignment.location的列,只有item.officeassignment不是null。 (因为这是一个零零或一关系,可能没有相关的甲式章程。)
    <td>
        @If item.OfficeAssignment IsNot Nothing Then
            @item.OfficeAssignment.Location 
        End If
    </td>
  • 添加的代码,可动态添加class ="success"到所选教练的TR元素。这为使用a设置所选行的背景颜色  引导  class.
    Dim selectedRow = String.Empty 
    If item.ID = ViewBag.InstructorID  这 n selectedRow = "success" 
        @<tr class="@selectedRow">
  • 添加了标有新的ActionLink  选择  在每行中的其他链接之前立即导致所选的教练ID发送到索引方法。

运行应用程序并选择 教师 标签。页面显示  地点  property of related 甲式甲型法官 当没有相关的情况下,实体和空桌面单元 甲式甲型法官 entity.

MVC5与EF6.

在里面  视图\ extender \ index.vbhtml 文件,关闭表元素(在文件末尾)后,添加以下代码。当选择一个教练这个代码显示的相关讲师课程列表。

@If Model.Courses IsNot Nothing Then
    @<h3> 课程  Taught by Selected Instructor</h3>
    @<table class="table">
        <tr>
            <th></th>
            <th> 数字 </th>
            <th>Title</th>
            <th> 部门 </th>
        </tr>

        @For Each item Model.Courses
                Dim selectedRow = String.Empty
                If item.CourseID = ViewBag.CourseID  这 n selectedRow = "success"

            @<tr class="@selectedRow">
                <td>
                    @Html.ActionLink("Select", "Index", New With {.courseID = item.CourseID})
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr>
        Next

    </table>
End If

此代码读取  课程  查看模型的属性显示课程列表。它还提供了一个  选择 发送的超链接 ID 所选课程到  指数  action method.

运行页面并选择一个教练。现在,您可以看到一个网格,显示分配给所选教师的课程,以及您看到所指定部门名称的每个课程。

MVC5与EF6.

在刚刚添加的代码块后,添加以下代码。这将显示选中该课程的课程中注册的学生列表。

@If Model.Enrollments IsNot Nothing Then

    @<h3> 学生 s Enrolled in Selected Course</h3>
    @<table class="table">
        <tr>
            <th> 名称 </th>
            <th>Grade</th>
        </tr>
        @For Each item Model.Enrollments

            @<tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(Function(modelItem) item.Grade)
                </td>
            </tr>
        Next
    </table>
End If

此代码读取 招收 视图模型的属性,以便在课程中展示注册的学生列表。

运行页面并选择一个教练。然后选择一个课程以查看注册学生和成绩的列表。

MVC5与EF6.

添加显式加载

打开  ChinanceRController.vb. 并查看索引方法如何获取所选课程的注册列表:

当您检索导师列表时,您指定了急切加载   课程  导航属性和  部门  每门课程的财产。然后你把  课程  在视图模型中集合,现在正在访问 招收 来自该集合中的一个实体的导航属性。因为您没有指定渴望加载 课程 导航属性,由于延迟加载,该属性的数据显示在页面中。

如果您禁用延迟加载,请在不改变代码的情况下以任何其他方式更改代码 招收 property would be 没有什么 无论课程实际上有多少次入学。在这种情况下,加载 招收 属性,您必须指定急切加载或显式加载。你已经看到了如何渴望加载。为了看到显式加载的示例,更换  指数  具有以下代码的方法,它明确加载 招收 财产。更改的代码突出显示。

Function  指数 (ByVal   ID   As Integer?, ByVal  课程  As Integer?) As ActionResult
    Dim viewModel = New  讲师 IndexData()
    viewModel.Instructors = db.Instructors _
        .Include(Function(i) i.OfficeAssignment) _
        .Include(Function(i) i.Courses.Select(Function(c) c.Department)) _
        .OrderBy(Function(i) i.LastName)
     If   ID  .HasValue  这 n
        ViewBag.InstructorID = id.Value
        viewModel.Courses = viewModel.Instructors.Where(Function(i) i.ID = id.Value).Single().Courses
     End If
     If  课程 .HasValue  这 n
        ViewBag.CourseID = courseID.Value
        '**Lazy Loading**
        'viewModel.Enrollments = viewModel.Courses _
        '.Where(Function(x) x.CourseID = courseID).Single().Enrollments()

        '**Explicit Loading**
        Dim selectedCourse = viewModel.Courses.Single(Function(x) x.CourseID = courseID)
        db.Entry(selectedCourse).Collection(Function(x) x.Enrollments).Load()
        For Each e selectedCourse.Enrollments
             db.Entry(e).Reference(Function(x) x.Student).Load()
        Next
        viewModel.Enrollments = selectedCourse.Enrollments
    End If
    Return View(viewModel)
End Function

获得所选后  课程  实体,新代码明确加载该课程 招收 navigation property:

db.Entry(selectedCourse).Collection(Function(x) x.Enrollments).Load()

然后它明确加载每个  注册  entity's related  学生  entity:

db.Entry(e).Reference(Function(x) x.Student).Load()

请注意,您使用的  收藏  加载收集属性的方法,但对于只有一个实体的属性,使用  参考  method.

运行讲师索引页面,您将在页面上显示的内容中没有区别,尽管您更改了如何检索数据。

概括

您现在已经使用了所有三种方式(懒惰,渴望和显式)将相关数据加载到导航属性中。在里面 下一个教程 您将学习如何更新相关数据。