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 属性。

运行项目,你会发现页面呈现数据并没有什么不一样的地方,但是其实我们已经更改了数据的检索方式。


我的小站:MVC5 Entity Framework学习(7):读取相关数据

还大家一个健康的网络环境,从你我做起

THE END

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: .NET MVC5是一种用于构建Web应用程序的框架,它基于Microsoft .NET平台。其中,Admin部分通常用于管理和维护应用程序的后台功能。 .NET MVC5 Admin可以让开发人员轻松创建一个带有管理员功能的后台管理系统。这个管理员后台系统可以用来管理各种数据,例如用户信息、产品列表、订单信息等等。使用MVC5 Admin,开发人员可以快速构建一个功能强大、安全可靠的后台管理系统。 .NET MVC5 Admin提供了一些重要的功能和特性。首先,它提供了身份验证和授权功能,可以通过身份验证来保护管理员后台。只有经过身份验证的用户才能访问后台系统,确保了系统的安全性。 其次,.NET MVC5 Admin还提供了简单易用的用户界面和操作方式。开发人员可以使用预定义的模板和组件来创建页面和控件,从而大大减少了开发时间和工作量。 此外,.NET MVC5 Admin还支持自定义和个性化的功能。开发人员可以根据具体的需求进行修改和扩展,以满足不同的业务要求。 总而言之,.NET MVC5 Admin是一种强大的工具,可以帮助开发人员快速、高效地构建后台管理员系统。它具有安全性高、易用性好、可扩展性强等特点,适用于各种规模的Web应用程序。无论是小型的个人网站,还是大型的企业门户,.NET MVC5 Admin都可以满足各种需求,提供一个出色的管理员后台系统。 ### 回答2: .NET MVC5 Admin是一个基于.NET MVC5框架的管理员控制面板。 .NET框架是由微软公司开发的一个开发平台,用于构建应用程序。MVC(Model-View-Controller)是一种设计模式,将应用程序分为模型、视图和控制器三个部分,使应用程序更加可维护和可扩展。 Admin是指管理员,通常拥有对系统进行管理和维护的权限。在.NET MVC5 Admin中,管理员可以通过控制面板对系统进行配置和管理。 .NET MVC5 Admin提供了一系列功能,例如用户管理、角色管理、权限管理、日志管理等。管理员可以通过用户管理来管理和控制系统的用户,包括添加用户、删除用户、编辑用户信息等操作。角色管理可以帮助管理员定义不同的角色,并分配相关权限。权限管理可以让管理员对不同的角色设置不同的操作权限,以保证系统安全和合规性。日志管理记录了系统的操作日志,方便管理员进行追踪和审计。 .NET MVC5 Admin还可以集成其他功能模块,例如文件管理、邮件发送等。管理员可以根据具体需求进行扩展和定制。 总之,.NET MVC5 Admin是一个基于.NET MVC5框架的管理员控制面板,提供了用户管理、角色管理、权限管理、日志管理等功能,方便管理员对系统进行配置和管理。它的灵活性和可扩展性使得管理员可以根据具体需求进行定制和拓展。 ### 回答3: .NET MVC5是一种用于构建Web应用程序的框架,而“admin”指的是应用程序的管理员界面或管理功能。在.NET MVC5中,可以使用一些技术来实现管理员界面。以下是一种可能的方法: 首先,可以创建一个用于管理功能的控制器。该控制器将处理所有与管理员相关的操作,例如创建、编辑和删除数据。接下来,可以创建相应的视图,用于显示管理员操作所需的页面和表单。 在视图中,可以使用.NET MVC提供的HTML助手和页面布局来构建用户界面。例如,可以使用HTML表单控件来创建输入字段,使用HTML按钮来提交表单数据。此外,还可以使用CSS和JavaScript来美化和增强管理员界面的外观和交互性。 在控制器中,可以使用.NET MVC提供的模型绑定功能来处理输入数据,并使用Linq或Entity Framework数据访问技术与数据库进行交互。通过这种方式,可以对管理员功能进行CRUD操作(创建、读取、更新和删除数据)。 此外,可以使用身份验证和授权技术来限制对管理员功能的访问。例如,可以使用ASP.NET身份验证来验证管理员的身份,并使用角色基于访问控制来限制不同角色的管理员所能执行的操作。 总结起来,通过使用.NET MVC5框架和相关技术,可以很容易地构建一个功能强大的管理员界面。该界面可以用来管理数据、执行各种操作并对用户进行身份验证和授权。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值