Чтение связанных данных с помощью Entity Framework в приложении MVC ASP.NET (5 из 10)

Том Дайкстра

Пример веб-приложения Contoso University демонстрирует создание ASP.NET приложений MVC 4 с помощью Entity Framework 5 Code First и Visual Studio 2012. Сведения о серии руководств см. в первом руководстве серии.

Примечание

Если у вас возникла проблема, которую не удается устранить, скачайте завершенную главу и попробуйте воспроизвести проблему. Как правило, решение проблемы можно найти, сравнив код с готовым кодом. Сведения о некоторых распространенных ошибках и способах их устранения см. в статье Ошибки и обходные пути.

В предыдущем руководстве вы завершили модель данных School. В этом руководстве вы прочитаете и отобразите связанные данные, то есть данные, которые Entity Framework загружает в свойства навигации.

На следующих рисунках изображены страницы, с которыми вы будете работать.

Снимок экрана, на котором показана страница индекса курсов университета Contoso.

Снимок экрана: страница индекса преподавателей университета Contoso с выбранным преподавателем и одним из выбранных курсов.

Платформа Entity Framework может загружать связанные данные в свойства навигации сущности несколькими способами.

  • Отложенная загрузка. При первом чтении сущности связанные данные не извлекаются. Однако при первой попытке доступа к свойству навигации необходимые для этого свойства навигации данные извлекаются автоматически. Это приводит к отправке нескольких запросов к базе данных — по одному для самой сущности и по одному при каждом получении связанных данных для сущности.

    Lazy_loading_example

  • Безотложная загрузка. При чтении сущности связанные данные извлекаются вместе с ней. Обычно такая загрузка представляет собой одиночный запрос с соединением, который получает все необходимые данные. Укажите неотложную загрузку Include с помощью метода .

    Eager_loading_example

  • Явная загрузка. Это похоже на отложенную загрузку, за исключением того, что вы явным образом извлекаете связанные данные в коде; Это не происходит автоматически при доступе к свойству навигации. Связанные данные загружают вручную, получая запись диспетчера состояний объектов для сущности и вызывая Collection.Load метод для коллекций или Reference.Load метод для свойств, которые содержат одну сущность. (В следующем примере, если вы хотите загрузить свойство навигации "Администратор", замените Collection(x => x.Courses)Reference(x => x.Administrator)на .)

    Explicit_loading_example

Так как они не получают значения свойств сразу, отложенная загрузка и явная загрузка также называются отложенной загрузкой.

Как правило, если вы знаете, что вам нужны связанные данные для каждой полученной сущности, то безотложная загрузка обеспечивает наилучшую производительность, так как один запрос, отправленный в базу данных, обычно более эффективен, чем отдельные запросы для каждой полученной сущности. Например, в приведенных выше примерах предположим, что каждый отдел имеет десять связанных курсов. Пример с неотложной загрузкой приведет только к одному запросу (присоединению) и одному циклу к базе данных. Примеры отложенной загрузки и явной загрузки приводят к одиннадцати запросам и одиннадцати круговых путей к базе данных. При высокой задержке дополнительные циклы приема-передачи данных особенно сильно влияют на производительность.

С другой стороны, в некоторых сценариях отложенная загрузка является более эффективной. Неотложная загрузка может привести к созданию очень сложного соединения, которое SQL Server не может эффективно обрабатываться. Или если требуется доступ к свойствам навигации сущности только для подмножества обрабатываемых вами наборов сущностей, отложенная загрузка может работать лучше, так как при неотложной загрузке будет получено больше данных, чем требуется. Если важна производительность, то для выбора наилучшего решения рекомендуется протестировать производительность для обоих случаев.

Как правило, явная загрузка используется только в том случае, если отложенная загрузка отключена. Один из сценариев, когда следует отключить отложенную загрузку, — это во время сериализации. Отложенная загрузка и сериализация не очень хорошо сочетаются, и если вы не будете осторожны, вы можете в конечном итоге запросить значительно больше данных, чем предполагалось, если включена отложенная загрузка. Сериализация обычно выполняется путем доступа к каждому свойству экземпляра типа. Доступ к свойствам запускает отложенную загрузку, и эти отложенные загруженные сущности сериализуются. Затем процесс сериализации обращается к каждому свойству отложенных сущностей, что может привести к еще более отложенной загрузке и сериализации. Чтобы предотвратить эту цепную реакцию, отключите отложенную загрузку перед сериализизовывом сущности.

Класс контекста базы данных выполняет отложенную загрузку по умолчанию. Отключить отложенную загрузку можно двумя способами:

  • Для определенных свойств навигации virtual опустите ключевое слово при объявлении свойства.

  • Для всех свойств навигации задайте значение LazyLoadingEnabledfalse. Например, можно поместить следующий код в конструктор класса контекста:

    this.Configuration.LazyLoadingEnabled = false;
    

Отложенная загрузка может маскирует код, который вызывает проблемы с производительностью. Например, код, который не задает неотложную или явную загрузку, но обрабатывает большой объем сущностей и использует несколько свойств навигации в каждой итерации, может быть очень неэффективным (из-за большого количества циклов передачи к базе данных). Приложение, которое хорошо работает в разработке с помощью локального сервера SQL Server, может иметь проблемы с производительностью при перемещении в базу данных Azure SQL из-за увеличения задержки и отложенной загрузки. Профилирование запросов базы данных с реалистичной тестовой нагрузкой поможет определить, подходит ли отложенная загрузка. Дополнительные сведения см. в разделах Demystifying Entity Framework Strategies: Loading Related Data и Using the Entity Framework to Reduce Network Latency to SQL Azure.

Создание страницы индекса курсов, на котором отображается название отдела

Сущность Course включает свойство навигации, которое содержит сущность Department кафедры, к которому привязан курс. Чтобы отобразить имя назначенного отдела в списке курсов, необходимо получить Name свойство из сущности Department , которая находится в свойстве навигации Course.Department .

Создайте контроллер с именем CourseController для Course типа сущности, используя те же параметры, что и для контроллера, как показано на Student следующем рисунке (за исключением того, что в отличие от изображения, класс контекста находится в пространстве имен DAL, а не в пространстве имен Models):

Add_Controller_dialog_box_for_Course_controller

Откройте Файл Controllers\CourseController.cs и просмотрите Index метод :

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

В автоматически сформированном шаблоне установлена безотложная загрузка свойства навигации Department при помощи метода Include.

Откройте Views\Course\Index.cshtml и замените существующий код следующим кодом. Изменения выделены:

@model IEnumerable<ContosoUniversity.Models.Course>

@{
    ViewBag.Title = "Courses";
}

<h2>Courses</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th></th>
        <th>Number</th>
        <th>Title</th>
        <th>Credits</th>
        <th>Department</th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <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>
        <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>
    </tr>
}
</table>

Мы внесли следующие изменения в код шаблона:

  • Изменен заголовок с Index (Индекс) на Courses (Курсы).
  • Перемещены ссылки на строки влево.
  • Добавлен столбец под заголовком Число , отображающий CourseID значение свойства. (По умолчанию первичные ключи не являются шаблонами, так как обычно они не имеют смысла для конечных пользователей. Однако в этом случае первичный ключ имеет смысл, и вы хотите показать его.)
  • Изменен заголовок последнего столбца с DepartmentID (имя внешнего ключа на Department сущность) на Department.

Обратите внимание, что для последнего столбца шаблонный код отображает Name свойство сущности Department , загруженной в свойство навигации Department :

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

Запустите страницу (выберите вкладку Курсы на домашней странице Университета Contoso), чтобы просмотреть список с названиями отделов.

Courses_index_page_with_department_names

Создание страницы индекса преподавателей, на которую отображаются курсы и регистрации

В этом разделе вы создадите контроллер и представление для сущности Instructor , чтобы отобразить страницу Индекса преподавателей:

Снимок экрана: страница

Эта страница считывает и отображает связанные данные следующим образом:

  • Список преподавателей отображает связанные данные сущности OfficeAssignment. Между сущностями Instructor и OfficeAssignment действует связь один к нулю или к одному. Для сущностей OfficeAssignment установлена безотложная загрузка. Как упоминалось ранее, безотложная загрузка обычно эффективнее при получении связанных данных для всех строк главной таблицы. В нашем случае мы хотим отобразить принадлежность к кабинету для каждого преподавателя.
  • Когда пользователь выбирает преподавателя, отображаются связанные сущности Course. Между сущностями Instructor и Course действует связь многие ко многим. Для сущностей Course и связанных сущностей Department используется безотложная загрузка. В этом случае отложенная загрузка может быть более эффективной, так как вам нужны курсы только для выбранного преподавателя. Этот пример, однако, показывает, как использовать безотложную загрузку для свойств навигации сущностей, которые сами находятся в свойствах навигации.
  • Когда пользователь выбирает курс, отображаются связанные данные из набора сущностей Enrollments. Между сущностями Course и Enrollment действует связь один ко многим. Вы добавите явную загрузку для Enrollment сущностей и связанных с ними Student сущностей. (Явная загрузка не требуется, так как включена отложенная загрузка, но здесь показано, как выполнить явную загрузку.)

Создание модели представления для представления индекса преподавателя

На странице Индекс преподавателя отображаются три разных таблицы. Таким образом, мы создаем модель представления, которая включает три свойства, каждое из которых содержит данные из одной таблицы.

В папке 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; }
    }
}

Добавление стиля для выбранных строк

Чтобы пометить выделенные строки, требуется другой цвет фона. Чтобы предоставить стиль для этого пользовательского интерфейса, добавьте следующий выделенный код в раздел /* info and errors */Content\Site.css, как показано ниже:

/* info and errors */
.selectedrow 
{ 
    background-color: #a4d4e6; 
}
.message-info {
    border: 1px solid;
    clear: both;
    padding: 10px 20px;
}

Создание контроллера и представлений преподавателя

Создайте InstructorController контроллер, как показано на следующем рисунке:

Add_Controller_dialog_box_for_Instructor_controller

Откройте файл Controllers\InstructorController.cs и добавьте инструкцию usingViewModels для пространства имен:

using ContosoUniversity.ViewModels;

Шаблонный код в методе Index задает неотложную загрузку только для свойства навигации OfficeAssignment :

public ViewResult 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.InstructorID == 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), который предоставляет значения идентификаторов выбранного преподавателя и выбранного курса, а также передает все необходимые данные в представление. Параметры передаются гиперссылками Select на странице.

Совет

Данные маршрута

Данные маршрута — это данные, которые связыватель модели обнаружил в сегменте URL-адреса, указанном в таблице маршрутизации. Например, маршрут по умолчанию задает сегменты controller, actionи id :

routes.MapRoute(  
 name: "Default",  
 url: "{controller}/{action}/{id}",  
 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }  
);

В следующем URL-адресе маршрут по умолчанию сопоставляется Instructor как controller, как action и Index 1 как id; это значения данных маршрута.

http://localhost:1230/Instructor/Index/1?courseID=2021

"?courseID=2021" — это строковое значение запроса. Связыватель модели также будет работать, если передать в id качестве значения строки запроса:

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

URL-адреса создаются операторами ActionLink в представлении Razor. В следующем коде id параметр соответствует маршруту по умолчанию, поэтому id добавляется в данные маршрута.

@Html.ActionLink("Select", "Index", new { id = item.PersonID  })

В следующем коде courseID не соответствует параметру в маршруте по умолчанию, поэтому он добавляется в качестве строки запроса.

@Html.ActionLink("Select", "Index", new { courseID = item.CourseID })

Код начинается с создания экземпляра модели представления и помещения его в список преподавателей. Код задает неотложную загрузку для свойства навигации Instructor.OfficeAssignmentInstructor.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 метод загружает Courses и для каждого загружаемого курса выполняет неотложную загрузку для свойства навигации Course.Department .

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

Как упоминалось ранее, загрузка не требуется, но выполняется для повышения производительности. Так как для представления всегда требуется сущность OfficeAssignment, значительно эффективнее извлекать ее в том же запросе. Course Сущности являются обязательными при выборе преподавателя на веб-странице, поэтому неотложная загрузка лучше, чем отложенная загрузка, только если страница отображается чаще с выбранным курсом, чем без.

Если был выбран идентификатор преподавателя, выбранный инструктор извлекается из списка преподавателей в модели представления. Затем из свойства навигации Courses этого преподавателя загружается свойство модели представления Courses вместе с сущностями Course.

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

Метод Where возвращает коллекцию, но в этом случае условия, передаваемые этому методу, приводят к возврату только одной Instructor сущности. Метод Single преобразует коллекцию в отдельную сущность Instructor, что позволяет получить доступ к ее свойству Courses.

Метод Single используется в коллекции, если известно, что в коллекции будет только один элемент. Метод Single создает исключение, если переданная ему коллекция пуста или имеет несколько элементов. Альтернативой является SingleOrDefault, которая возвращает значение по умолчанию (null в данном случае), если коллекция пуста. Однако в этом случае это по-прежнему приведет к возникновению исключения (при попытке найти Courses свойство в null ссылке), а сообщение об исключении будет менее четко указывать причину проблемы. При вызове Single метода можно также передать Where условие, а не вызывать Where метод отдельно:

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

вместо следующего кода:

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

Далее, если был выбран курс, то он получается из списка курсов модели представления. Затем свойство модели Enrollments представления загружается с Enrollment сущностями из свойства навигации Enrollments этого курса.

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

Изменение представления индекса преподавателя

В views\Instructor\Index.cshtml замените существующий код следующим кодом. Изменения выделены:

@model ContosoUniversity.ViewModels.InstructorIndexData

@{
    ViewBag.Title = "Instructors";
}

<h2>Instructors</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table> 
    <tr> 
        <th></th> 
        <th>Last Name</th> 
        <th>First Name</th> 
        <th>Hire Date</th> 
        <th>Office</th>
    </tr> 
    @foreach (var item in Model.Instructors) 
    { 
        string selectedRow = ""; 
        if (item.InstructorID == ViewBag.InstructorID) 
        { 
            selectedRow = "selectedrow"; 
        } 
        <tr class="@selectedRow" valign="top"> 
            <td> 
                @Html.ActionLink("Select", "Index", new { id = item.InstructorID }) | 
                @Html.ActionLink("Edit", "Edit", new { id = item.InstructorID }) | 
                @Html.ActionLink("Details", "Details", new { id = item.InstructorID }) | 
                @Html.ActionLink("Delete", "Delete", new { id = item.InstructorID }) 
            </td> 
            <td> 
                @item.LastName 
            </td> 
            <td> 
                @item.FirstMidName 
            </td> 
            <td> 
                @Html.DisplayFor(modelItem => item.HireDate)
            </td> 
            <td> 
                @if (item.OfficeAssignment != null) 
                { 
                    @item.OfficeAssignment.Location  
                } 
            </td> 
        </tr> 
    } 
</table>

Мы внесли следующие изменения в существующий код:

  • Изменили класс модели на InstructorIndexData.

  • Изменили заголовок страницы с Index на Instructors.

  • Перемещены столбцы ссылок на строки влево.

  • Удален столбец FullName .

  • Добавлен столбец Office , который отображается item.OfficeAssignment.Location только в том случае, если item.OfficeAssignment значение не равно NULL. (Так как это отношение "один к нулю" или "один", может не быть связанной OfficeAssignment сущности.)

    <td> 
        @if (item.OfficeAssignment != null) 
        { 
            @item.OfficeAssignment.Location  
        } 
    </td>
    
  • Добавлен код, который будет динамически добавляться class="selectedrow" в tr элемент выбранного преподавателя. При этом задается цвет фона для выбранной строки с помощью созданного ранее класса CSS. (Атрибут valign будет полезен в следующем руководстве при добавлении многострочного столбца в таблицу.)

    string selectedRow = ""; 
    if (item.InstructorID == ViewBag.InstructorID) 
    { 
        selectedRow = "selectedrow"; 
    } 
    <tr class="@selectedRow" valign="top">
    
  • Добавлена новая ActionLink метка Select непосредственно перед другими ссылками в каждой строке, что приводит к отправке выбранного идентификатора преподавателя в Index метод.

Запустите приложение и выберите вкладку Преподаватели. Если связанной OfficeAssignment сущности нет, Location на странице отображается свойство связанных OfficeAssignment сущностей и пустая ячейка таблицы.

Instructors_index_page_with_nothing_selected

В файле Views\Instructor\Index.cshtml после закрывающего table элемента (в конце файла) добавьте следующий выделенный код. Отобразится список курсов, связанных с инструктором при выборе преподавателя.

<td> 
                @if (item.OfficeAssignment != null) 
                { 
                    @item.OfficeAssignment.Location  
                } 
            </td> 
        </tr> 
    } 
</table>

@if (Model.Courses != null) 
{ 
    <h3>Courses Taught by Selected Instructor</h3> 
<table> 
    <tr> 
        <th></th> 
        <th>ID</th> 
        <th>Title</th> 
        <th>Department</th> 
    </tr> 
 
    @foreach (var item in Model.Courses) 
    { 
        string selectedRow = ""; 
        if (item.CourseID == ViewBag.CourseID) 
        { 
            selectedRow = "selectedrow"; 
        } 
    <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> 
}

Этот код считывает свойство Courses модели представления для отображения списка курсов. Он также предоставляет гиперссылку Select , которая отправляет идентификатор выбранного курса в Index метод действия.

Примечание

CSS-файл кэшируется браузерами. Если при запуске приложения изменения не отображаются, выполните жесткое обновление (удерживайте нажатой клавишу CTRL при нажатии кнопки Обновить или нажмите клавиши CTRL+F5).

Запустите страницу и выберите преподавателя. Вы увидите сетку, которая отображает курсы, назначенные выбранному преподавателю, и для каждого курса отобразится имя связанного факультета.

Instructors_index_page_with_instructor_selected

После только что добавленного блока кода добавьте следующий код. Он отображает список студентов, которые зачислены на курс при выборе этого курса.

@if (Model.Enrollments != null) 
{ 
    <h3> 
        Students Enrolled in Selected Course</h3> 
    <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 модели представления для отображения списка студентов, зачисленных на этот курс.

Запустите страницу и выберите преподавателя. Затем выберите курс, чтобы увидеть список зачисленных студентов и их оценки.

Снимок экрана: страница

Добавление явной загрузки

Откройте файл InstructorController.cs и посмотрите, как Index метод получает список регистраций для выбранного курса:

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

При получении списка преподавателей вы указали неотложную загрузку Courses для свойства навигации и свойства Department каждого курса. Затем вы помещаете коллекцию Courses в модель представления и обращаетесь к свойству навигации Enrollments из одной сущности в этой коллекции. Так как вы не указали неотложную загрузку Course.Enrollments для свойства навигации, данные из этого свойства отображаются на странице в результате отложенной загрузки.

Если вы отключили отложенную загрузку, не изменяя код каким-либо другим способом, Enrollments свойство будет иметь значение NULL независимо от того, сколько регистраций на самом деле было в курсе. В этом случае для загрузки Enrollments свойства необходимо указать либо неотложную загрузку, либо явную загрузку. Вы уже видели, как выполнять неотложную загрузку. Чтобы увидеть пример явной загрузки, замените Index метод следующим кодом, который явно загружает Enrollments свойство . Измененный код выделен.

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.InstructorID == id.Value).Single().Courses;
    }
    
    if (courseID != null)
    {
        ViewBag.CourseID = courseID.Value;
        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 сущности новый код явно загружает свойство навигации Enrollments этого курса:

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

Затем он явно загружает связанную Student сущность каждой Enrollment сущности:

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

Обратите внимание, что для загрузки свойства коллекции используется Collection метод , а для свойства, которое содержит только одну сущность, используется Reference метод . Вы можете запустить страницу Индекс преподавателя сейчас, и вы не увидите разницы в том, что отображается на странице, хотя вы изменили способ получения данных.

Итоги

Теперь вы использовали все три способа (отложенный, неотложный и явный) для загрузки связанных данных в свойства навигации. В следующем руководстве вы узнаете, как обновлять связанные данные.

Ссылки на другие ресурсы Entity Framework можно найти в ASP.NET карте содержимого доступа к данным.