MVC 5 + EF 6(七)【載入相關資料】

風靈使發表於2018-07-10

1.延遲(Lazy)載入、預先(Eager)載入、顯式(Explicit)載入:

EF載入相關資料到實體導航屬性有以下幾種方式:

  • 延遲載入:當實體第一次讀取時,相關資料沒有載入。當第一次試圖訪問導航屬性時,所需的導航資料自動載入。這導致多條查詢語句被髮送到資料庫:一條查詢實體本身,一條查詢實體相關資料。DbContext類預設啟用延遲載入。

Lazy_loading_example

  • 預先載入:當讀取實體時,相關資料同時讀取。這通常會導致一個連線查詢,查詢所有所需的資料。使用Include方法指定預先載入。

Eager_loading_example

  • 顯示載入:這種載入方式類似於延遲載入,不同的是我們要在程式碼中明確地查詢相關資料;當我們訪問導航屬性時,它不會自動查詢。我們需要手動獲取實體的物件狀態管理器入口,並且需要呼叫Collection.Load方法獲取集合或者呼叫Reference.Load方法獲取單個實體來載入相關資料(在下面的例子中如果我們想要獲取Administrator導航屬性,使用Reference(x => x.Administrator)代替Collection(x => x.Courses))。通常情況下,我們不需要使用顯示載入,除非禁用了延遲載入。
    Explicit_loading_example  

因為不立即獲取屬性值,延遲載入和顯式載入同時又被稱為延後(deferred)載入。

1.1.考慮效能:

如果我們知道,我們需要每個實體的相關資料,預先載入大多數情況下會有最好的效能,因為一條查詢資料通常比單獨查詢每個實體更有效率。例如,在上面的例子中,假設每個department有10相關的course。預先載入載入資料只產生一條(連線)查詢語句和1次往返。延遲載入和顯式載入載入資料均會產生11條查詢語句和11次往返。額外的往返尤其不利於效能的提高。

另一方面,在一些情況下,延遲載入會更有效率。預先載入可能會產生一個SQL Server不能有效處理的非常複雜的連線。或者,我們只是需要訪問導航屬性的子集,延遲載入會比預先載入效能高,因為預先載入返回的資料比我們實際需要的要多。如果效能是至關重要的,最好同時測試這兩種方式的效能以作出做好的選擇。

延遲載入會遮掩程式碼這樣會造成效能問題。例如,程式碼沒有指定預先載入或者顯式載入,但是需要處理大量的實體,並且在每個迭代中使用多個導航屬性,這是效率將會非常低(因為多次往返資料庫)。

一個程式可能在開發時使用SQL Server效能表現良好,但是當部署在Azure SQL資料庫上時,由於增加了延遲和延遲載入,可能會遇到效能問題。使用實際負載測試分析資料庫查詢會幫助我們確定延遲載入是否適當。更多資訊請檢視:Demystifying Entity Framework Strategies: Loading Related Data和Using the Entity Framework to Reduce Network Latency to SQL Azure。

1.2.在序列化之前禁用延遲載入:

如果在序列化過程中延遲載入是啟用的,我們獲取到的資料會比我們想要的要多。序列化通常會訪問一個型別每個例項的每個屬性。對屬性的訪問會觸發延遲載入,延遲載入的實體被例項化。序列化過程訪問延遲載入實體的每個屬性,可能會會觸發更多的延遲載入和序列化。為了防止這種連鎖反應失控,我們要在序列化實體前禁用延遲載入。

EF使用的代理類也會使序列化變得複雜,請檢視:Advanced Scenarios tutorial。

一種避免序列化問題的方法是不序列化實體物件而是序列化資料傳輸物件(DTO),請檢視:Using Web API with Entity Framework。

如果不使用DTO,我們可以禁用延遲載入和避免代理,請檢視:disabling proxy creation。

禁用延遲載入的其他方法:

禁用特定的導航屬性的延遲載入,在定義時不使用virtual關鍵字。
對所有的導航屬性禁用延遲載入,在上下文類的建構函式中新增如下程式碼:

this.Configuration.LazyLoadingEnabled = false;

2.建立Course頁面,顯示DepartmentName

建立CourseController(選擇MVC 5 Controller with views, using Entity Framework):
這裡寫圖片描述

Index方法

public ActionResult Index()
{
    var courses = db.Courses.Include(c => c.Department);
    return View(courses.ToList());
}

修改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>

執行:
Courses_index_page_with_department_names

3.建立Instructors頁面顯示CoursesEnrollments

本節最終頁面:

Instructors_index_page_with_instructor_and_course_selected

3.1.為InstructorIndex檢視建立檢視模型:

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

3.2.建立Instructor控制器和檢視:

使用EF read/write建立InstructorController

Add_Controller_dialog_box_for_Instructor_controller

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

當我們知道集合中只有一個專案時我們使用Single方法。如果傳遞給集合的為空值或者多於一個專案Single方法會丟擲異常。SingleOrDefault方法會在集合為空值時返回一個預設值(在本例中會返回空值)。但是在本例中還是會丟擲異常(在一個空引用檢視搜尋Courses屬性時),並且異常訊息將不會標明問題的原因。如果我們使用Single方法時,我們同時可以給它傳遞Where條件,這樣可以不用再呼叫Where方法:

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

可以取代:

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

3.3.修改Index檢視:

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

Index檢視最後新增:

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

再次在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>
}

3.4.新增顯示載入:

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

原文地址

相關文章