Translation: Getting Started with Entity Framework 6 of MVC5 (7)-Reading Related Data for ASP.NET MVC Applications

Read related data for ASP.NET MVC applications

This is a translation of Microsoft's official tutorial Getting Started with Entity Framework 6 Code First using MVC 5 series, here is the seventh article: Reading related data for ASP.NET MVC applications

原文: Reading Related Data with the Entity Framework in an ASP.NET MVC Application


In the previous tutorial you have completed the school data model. In this tutorial you will learn how to read data related to reality-this refers to data loaded through the navigation properties of the Entity Framework.

The screenshot below shows the page you are about to complete.

coursesindex

ics

Delay, pre-load and realistic load related data

Entity framework has a variety of methods to load related data from an entity's navigation properties:

  • Lazy loading
    . When the entity is read for the first time, the relevant data will not be obtained. However, when you first try to access the navigation property, the data required by the navigation property is automatically loaded. The result will be sent to the database using multiple queries-once to read the entity itself, then each related entity. The DbContext class uses lazy loading by default.

Windows-Live-Writer_Reading-Re.NET-MVC-Application-5-of-10h1_ADC3_Lazy_loading_example_2c44eabb-5fd3-485a-837d-8e3d053f2c0c

  • Eager
    Loading. When an entity reads, it immediately obtains data related to the entity. This usually results in retrieving all the required data in a single connection query. You can specify the preload by using the Include method.

Windows-Live-Writer_Reading-Re.NET-MVC-Application-5-of-10h1_ADC3_Eager_loading_example_33f907ff-f0b0-4057-8e75-05a8cacac807

  • Explicit loading
    . It's a bit similar to lazy loading, except that you explicitly get relevant data in your code. When you access a navigation property, it will not load automatically. You need to manually load the relevant data by using the entity's object state manager and calling the Collection.Load method on the collection or by the Reference.Load method that holds the properties of a single entity. (In the following example, if you want to load the administrator navigation properties, you need to use Reference (x
    => x.Administrator) to replace Collection (x => x.Courses))

Windows-Live-Writer_Reading-Re.NET-MVC-Application-5-of-10h1_ADC3_Explicit_loading_example_79d8c368-6d82-426f-be9a-2b443644ab15

Because neither lazy loading nor explicit loading retrieves the value of an attribute immediately, they are also called deferred loading.

Performance considerations

If you know that you need the relevant data for each entity immediately, preloading usually provides the best performance. Because the efficiency of sending a single query to the database and fetching the data at once is usually higher than the efficiency of issuing another query on each entity. For example, in the above example, assuming that each department has ten related courses, pre-loading will result in only one query (join joint query) to and from the database. Both lazy loading and explicit loading will cause 11 queries and round trips. In the case of high latency, additional queries and round trips are often disadvantageous.

On the other hand, using lazy loading is more efficient in some cases. Pre-loading may cause very complex join queries that SQL Server cannot effectively handle. Or, if you are dealing with the navigation attribute of an entity that needs to be accessed, this attribute is only a subset of the entity set, lazy loading may be better than preloading, because preloading will load all the data, Even if you don't need to access them. If the performance of the application is extremely important, you'd better test and choose the best between these two methods.

Lazy loading may block some code that causes performance problems. For example, the code does not specify pre-loading or explicit loading but when processing a large number of entities and using navigation attributes in each iteration, the efficiency of the code may be low (because there will be a large number of database round-trip queries). An application that performs well in a development environment may experience a decrease in the performance of lazy loading due to increased latency when moving to a Windows Azure SQL database. You should analyze and test to ensure that lazy loading is appropriate. For more information, see Demystifying Entity Framework Strategies: Loading Related Data and Using the Entity Framework to Reduce Network Latency to SQL Azure .

Disable lazy loading before serialization

If you enable lazy loading during serialization, you may end up querying more data than expected. Serialization generally accesses every attribute of the class. The attribute access triggers lazy loading, and then the lazy loaded entities are also serialized. In the end, it may cause more lazy loading and serialization of attributes. To prevent this chain reaction, please disable lazy loading before the entity is serialized.

One way to avoid serialization problems is to serialize data transfer objects (DTOs) instead of entity objects, as shown in the Using Web API with Entity Framework tutorial.

If you do not want to use DTO, you can disable lazy loading and avoid disabling proxy creation to avoid proxy problems.

Here are some ways to disable lazy loading:

  • For specific navigation attributes, omit the virtual keyword declaration.
  • For all navigation properties, you can set LazyLoadingEnabled to false, and put the following code in the constructor of your context class:
this.Configuration.LazyLoadingEnabled = false;

Create course page showing department name

The Course entity contains a navigation attribute that includes the Department entity assigned to the course. To display the name of the assigned department in the course list, you need to obtain the Name property from the Department entity, which is the Course.Department navigation property.

Create a new "MVC 5 Controller with Views (using Entity Framework)" controller for the Course entity type and name it CourseController, using the same settings as you created the Student controller before, as shown in the following figure:

coursecontroller

Open the controller and view the Index method:

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

You see that the automatically generated scaffolding uses the Include method to specify the preloading of the Department attribute.

Open Views \ Course \ Index.cshtml and replace the original with the following code:

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

You made the following changes to the scaffolding code:

  • Change the title from Index to Course.
  • Added a numeric line to display the CourseID attribute value. By default, scaffolding does not generate primary key templates, because usually they are meaningless to the end user. But in this case, we are only used to show that you can display it this way.
  • Moved the course row to the right and modified its title. The scaffolding correctly selected the Name attribute of the Department entity, but on this course page, the column title should be Department, not Name.

Please note that in the department row, the scaffolding code shows that the Name property of the department entity is loaded through the navigation property.

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

Run this page (select the course tab) to view the list of department names.

coursesindex

Create an instructor page to display course and registration information

In this section you will create a controller and use the lecturer entity view to display the lecturer page.

ics

This page reads and related data in the following ways:

  • The instructor list displays the relevant data of the OfficeAssignment entity. There is a one-to-one or zero relationship between the Instructor and OfficeAssignment entities, you can use the OfficeAssignment entity amount to pre-load. As mentioned earlier, when you need all the related data of the main table, pre-loading is more effective. Here, you want to display the office assignments of all lecturers.
  • When the user selects a lecturer, the related Course entity will be displayed. There is a many-to-many relationship between Instructor and Course entities. You can also use preloading on Course entities and their related Department entities. But here, lazy loading may be more effective, because you only need the course information of the selected instructor. In fact, here demonstrates how to use lazy loading to load navigation properties among navigation properties.
  • When the user selects a course, the relevant data in the registered entity record is displayed. Course and Enrollment entities are in a one-to-many relationship. You will add explicit loading to Enrollment entities and their related Student entities. (Explicit loading is actually unnecessary, but here only shows how to perform explicit loading)

Create ViewModel for instructor indexed view

The instructor page shows three different tables, so you will create a view model with three attributes, each of which holds the data required by a table.

In the ViewModels folder, create InstructorIndexData.cs and replace the original with the following code:

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

Create instructor controllers and views

Like the previous CourseController controller, create an InstructorController controller, as shown below:

instructorcontroller

Open Controller \ InstructorController.cs and add the namespace reference of ViewModels:

using ContosoUniversity.ViewModels;

In the Index method code created by the scaffolding, only the OfficeAssignment navigation property is preloaded.

public ActionResult Index()
{
    var instructors = db.Instructors.Include(i => i.OfficeAssignment);
    return View(instructors.ToList());
}

Use the following code to replace the Index method to load other relevant data:

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

This method receives an optional routing parameter (id) and a query string parameter (courseID) to provide the ID value of the selected lecturer and course and passes all required data to the view. The selection hyperlink on the page will provide these parameters.

The code first creates an instance of the view model and puts the list of lecturers into the model. The code specifies the use of pre-loading on the OfficeAssignment and Courses navigation properties.

var viewModel = new InstructorIndexData();
viewModel.Instructors = db.Instructors
    .Include(i => i.OfficeAssignment)
    .Include(i => i.Courses.Select(c => c.Department))
     .OrderBy(i => i.LastName);

The second Include method loads courses and pre-loads Department navigation properties for each course.

.Include(i => i.Courses.Select(c => c.Department))

As mentioned earlier, unless it is to improve performance, pre-loading is not necessary. Since views always require OfficeAssgnment entities, it is more effective to process them in the same query. The course entity needs to be loaded when a lecturer is selected. Only the page displays the course more often than no choice. Preloading is better than lazy loading.

If a lecturer ID is selected, the selected lecturer will be retrieved from the list of view models. The Courses property of the view model loads the related Course entity through the Courses navigation property of the instructor.

if (id != null)
{
    ViewBag.InstructorID = id.Value;
    viewModel.Courses = viewModel.Instructors.Where(i => i.ID == id.Value).Single().Courses;
}

The Where method returns a collection but here only returns a lecturer entity. The Single method converts the collection into a lecturer entity, enabling you to access the Courses property of the entity.

When you know that the collection will contain only one element, you can use the Single method on the collection. An exception should be raised when you call the Single method on an empty collection or a collection with multiple elements. Another option is to use SingleOrDefault, if the collection is empty, it returns a default value. But using SingleOrDefault in this example will still cause an exception (will try to access the Courses property, but the property is a null reference) and the exception message will indicate this. When calling the Single method, you can also pass a Where condition instead of calling the Where and Single methods separately:

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

Instead of

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

Next, if a course is selected, the selected course will be retrieved from the view model's course list. Then read the registration entity from the registration navigation property of the course and load it into the registration property of the view model.

    if (courseID != null)
    {
        ViewBag.CourseID = courseID.Value;
        viewModel.Enrollments = viewModel.Courses.Where(
            x => x.CourseID == courseID).Single().Enrollments;
    }

Modify the lecturer index view

In Views \ Instructor \ Index.cshtml, replace the original with the following code:

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

You made the following changes to the code:

  • Change the view's model class to InstructorIndexData
  • Changed title
  • Added an office column to display Location when item.OfficeAssignent is not empty (because this is a one-to-one or zero relationship)
<td> 
    @if (item.OfficeAssignment != null) 
    { 
        @item.OfficeAssignment.Location  
    } 
</td> 
  • Dynamically add class = "success" to the tr element of the selected teacher through the code. Here, the background color of the selected row is set by using the Bootstrap style sheet
string selectedRow = ""; 
if (item.InstructorID == ViewBag.InstructorID) 
{ 
    selectedRow = "success"; 
} 
<tr class="@selectedRow" valign="top"> 
  • Add a new ActionLink to send the selected lecturer ID to the Index method.

Run the application and select the Instructor tab. The information of the instructor is displayed on the page, as well as the Location property when the OfficeAssignment entity is not empty. If there is no related office, nothing is displayed.

instructorsindex

In the Views \ Instructor \ Index.cshtml file, add the following code after the table element to display the course list of the selected lecturer

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

This code is used to read and display course attributes in the view module. It also provides a Select hyperlink to send the ID of the selected course to the Index method.

Run the page and select a lecturer. You will see a table showing the courses assigned to that lecturer.

ic

Add the following code after the code you just added to display the list of students enrolled in the selected course.

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

This code reads the Enrollments property of the view model and is used to display the students who register for the course.

Run the page and select a lecturer, then select a course to view registered students and their grades.

ics

Add explicit loading

Open InstructorController.cs and check how the Index method gets the registered list for the selected course:

    if (courseID != null)
    {
        ViewBag.CourseID = courseID.Value;
        viewModel.Enrollments = viewModel.Courses.Where(
            x => x.CourseID == courseID).Single().Enrollments;
    }

When you retrieved the list of lecturers, you specified preloading on the Courses navigation attribute and the department attribute of each course. Then you put the course collection into the view model, and now you can access it by registering navigation properties in the entity of the collection. Because you did not specify the preload of the Navigation.Enrollments navigation property, the data in this property will use lazy loading and will only be loaded when the page is rendered.

If you disable lazy loading without changing other code, the Enrollments property will be empty no matter how many registrations are actually made. In this case, if you want to load the Enrollments attribute, you must specify preloading or explicit loading. You have already seen how to use preloading. To demonstrate explicit loading, replace the original student part with the following code, we will use explicit loading on the Enrollments property.

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

After selecting the Course entity, the new code uses the Enrollments navigation property of the explicitly loaded course:

db.Entry(selectedCourse).Collection(x => x.Enrollments).Load();

Then explicitly load the Student entity associated with each Enrollment entity:

db.Entry(enrollment).Reference(x => x.Student).Load();

Please note that you use the Collection method to load the collection, but for attributes with only one entity, use the Reference method to load.

Now re-run the page and confirm that everything is working properly, but in fact you have changed the way the data is retrieved.

to sum up

You have now tried to load the relevant data into the navigation properties using delayed, pre-loaded and explicit loading methods. In the next tutorial, you will learn how to update the relevant data.

author information

tom-dykstra Tom Dykstra -Tom Dykstra is a senior programmer and writer on the Microsoft Web Platform and Tools team.

Published 40 original articles · 25 praises · 100,000+ views

Guess you like

Origin blog.csdn.net/yym373872996/article/details/53113785