Lectura de datos relacionados con Entity Framework en una aplicación MVC de ASP.NET (5 de 10)

por Tom Dykstra

En la aplicación web de ejemplo Contoso University, se enseña cómo crear aplicaciones ASP.NET MVC 4 con Code First de Entity Framework 5 y Visual Studio 2012. Para obtener información sobre la serie de tutoriales, consulte el primer tutorial de la serie.

Nota:

Si te encuentras con un problema que no puedes resolver, descarga el capítulo completado e intenta reproducir el problema. Por lo general, puedes encontrar la solución al problema comparando tu código con el código completado. Para conocer algunos errores comunes y cómo resolverlos, consulta Errores y soluciones alternativas.

En el tutorial anterior, completaste el modelo de datos School. En este tutorial podrás leer y mostrar datos relacionados, es decir, los datos que Entity Framework carga en propiedades de navegación.

En las ilustraciones siguientes se muestran las páginas con las que va a trabajar.

Screenshot that shows the Contoso University Courses Index page.

Screenshot that shows the Contoso University Instructors Index page with an instructor and one of their courses selected.

Hay varias formas con las que Entity Framework carga datos relacionados en las propiedades de navegación de una entidad:

  • Carga diferida. Cuando la entidad se lee por primera vez, no se recuperan datos relacionados. Pero la primera vez que intente obtener acceso a una propiedad de navegación, se recuperan automáticamente los datos necesarios para esa propiedad de navegación. Esto da como resultado varias consultas enviadas a la base de datos: una para la propia entidad y otra cada vez que se deben recuperar los datos relacionados de la entidad.

    Lazy_loading_example

  • Carga diligente. Cuando se lee la entidad, junto a ella se recuperan datos relacionados. Esto normalmente da como resultado una única consulta de combinación en la que se recuperan todos los datos que se necesitan. Especifica la carga diligente mediante el método Include.

    Eager_loading_example

  • Carga explícita. Es similar a la carga diferida, salvo que recupera explícitamente los datos relacionados en el código; no se produce automáticamente cuando se accede a una propiedad de navegación. Los datos relacionados se cargan manualmente obteniendo la entrada del administrador de estado de objetos para una entidad y llamando al método Collection.Load para las colecciones o al método Reference.Load para las propiedades que contienen una sola entidad. (En el ejemplo siguiente, si quieres cargar la propiedad de navegación Administrador, debes reemplazar Collection(x => x.Courses) por Reference(x => x.Administrator)).

    Explicit_loading_example

Dado que no recuperan inmediatamente los valores de propiedad, la carga diferida y la carga explícita también se conocen como carga aplazada.

En general, si sabes que necesitas datos relacionados para cada entidad que se recupere, la carga diligente ofrece el mejor rendimiento, dado que una única consulta que se envía a la base de datos suele ser más eficaz que consultas independientes para cada entidad recuperada. Por ejemplo, en los ejemplos anteriores, supongamos que cada departamento tiene diez cursos relacionados. El ejemplo de carga diligente daría lugar a una única consulta (de unión) y un único viaje de ida y vuelta a la base de datos. Los ejemplos de carga diferida y carga explícita generarían once consultas y once recorridos de ida y vuelta a la base de datos. Los recorridos de ida y vuelta adicionales a la base de datos afectan especialmente de forma negativa al rendimiento cuando la latencia es alta.

Por otro lado, en algunos escenarios la carga diferida es más eficaz. La carga diligente puede hacer que se genere una combinación muy compleja, que SQL Server no puede procesar de forma eficaz. O bien, si necesitas tener acceso a las propiedades de navegación de una entidad solo para un subconjunto de un conjunto de las entidades que estás procesando, es posible que las consultas independientes den mejores resultados porque la carga diligente de todo el contenido por adelantado recuperaría más datos de los que necesitas. Si el rendimiento es crítico, es mejor probarlo de ambas formas para elegir la mejor opción.

Normalmente, usarías la carga explícita solo cuando hayas desactivado la carga diferida. Un escenario en el que se debe desactivar la carga diferida es durante la serialización. La carga diferida y la serialización no se mezclan bien y, si no tiene cuidado, puedes terminar consultando significativamente más datos de lo previsto cuando se habilita la carga diferida. La serialización generalmente funciona accediendo a cada propiedad en una instancia de un tipo. El acceso a propiedades desencadena la carga diferida y esas entidades cargadas diferidas se serializan. A continuación, el proceso de serialización accede a cada propiedad de las entidades cargadas de forma diferida, lo que puede provocar una carga y serialización aún más diferida. Para evitar esta reacción en cadena de desencadenamiento, desactiva la carga diferida antes de serializar una entidad.

La clase de contexto de base de datos realiza la carga diferida de forma predeterminada. Hay dos maneras de deshabilitar la carga diferida:

  • Para propiedades de navegación específicas, omite la palabra clave virtual al declarar la propiedad.

  • Para todas las propiedades de navegación, establece LazyLoadingEnabled como false. Por ejemplo, puedes colocar el código siguiente en el constructor de la clase de contexto:

    this.Configuration.LazyLoadingEnabled = false;
    

La carga diferida puede enmascarar el código que provoca problemas de rendimiento. Por ejemplo, el código que no especifica la carga diligente o explícita, pero procesa un gran volumen de entidades y usa varias propiedades de navegación en cada iteración podría ser muy ineficaz (debido a muchos recorridos de ida y vuelta a la base de datos). Una aplicación que funciona bien en el desarrollo mediante un servidor SQL Server local podría tener problemas de rendimiento al moverse a Azure SQL Database debido al aumento de la latencia y la carga diferida. La generación de perfiles de las consultas de base de datos con una carga de prueba realista te ayudará a determinar si la carga diferida es adecuada. Para obtener más información, consulta Desmitificar estrategias de Entity Framework: carga de datos relacionados y Uso de Entity Framework para reducir la latencia de red a SQL Azure.

Creación de una página de índice de cursos en la que se muestre el nombre del departamento

La entidad Course incluye una propiedad de navegación que contiene la entidad Department del departamento al que se asigna el curso. Para mostrar el nombre del departamento asignado en una lista de cursos, tendrás que obtener la propiedad Name de la entidad Department que se encuentra en la propiedad de navegación Course.Department.

Crea un controlador denominado CourseController para el tipo de entidad Course, con las mismas opciones que elegiste anteriormente para el controlador Student, como se muestra en la ilustración siguiente (excepto que a diferencia de la imagen, la clase de contexto está en el espacio de nombres DAL, no en el espacio de nombres Models):

Add_Controller_dialog_box_for_Course_controller

Abre Controllers\CourseController.cs y examina el método Index:

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

El scaffolding automático ha especificado la carga diligente para la propiedad de navegación Department mediante el método Include.

Abre Views/Courses/Index.cshtml y reemplaza el código existente por el código siguiente. Se resaltan los cambios:

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

Ha realizado los cambios siguientes en el código con scaffolding:

  • Ha cambiado el título de Index a Courses.
  • Has movido los vínculos de fila a la izquierda.
  • Has agregado una columna bajo el encabezado Número que muestra el valor de la propiedad CourseID. (De forma predeterminada, las claves principales no tienen scaffolding porque normalmente no tienen sentido para los usuarios finales. Pero en este caso, la clave principal es significativa y quiere mostrarla).
  • Has cambiado el último encabezado de columna de DepartmentID (el nombre de la clave externa de la entidad Department) a Department.

Ten en cuenta que, para la última columna, el código con scaffolding muestra la propiedad Name de la entidad Department que se carga en la propiedad de navegación Department:

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

Ejecuta la página (selecciona la pestaña Cursos en la página principal de Contoso University) para ver la lista con los nombres de departamento.

Courses_index_page_with_department_names

Creación de una página de índice de instructores en la que se muestran los cursos y las inscripciones

En esta sección, crearás un controlador y una vista para la entidad Instructor con el fin de mostrar la página Índice de instructores:

Screenshot showing the Instructors Index page with an instructor and one of their courses selected.

En esta página se leen y muestran los datos relacionados de las maneras siguientes:

  • En la lista de instructores se muestran datos relacionados de la entidad OfficeAssignment. Las entidades Instructor y OfficeAssignment se encuentran en una relación de uno a cero o uno. Usará la carga diligente para las entidades OfficeAssignment. Como se explicó anteriormente, la carga diligente normalmente es más eficaz cuando se necesitan los datos relacionados para todas las filas recuperadas de la tabla principal. En este caso, quiere mostrar las asignaciones de oficina para todos los instructores que se muestran.
  • Cuando el usuario selecciona un instructor, se muestran las entidades Course relacionadas. Las entidades Instructor y Course se encuentran en una relación de varios a varios. Usará la carga diligente para las entidades Course y sus entidades Department relacionadas. En este caso, es posible que las consultas independientes sean más eficaces porque necesitas cursos solo para el instructor seleccionado. Pero en este ejemplo se muestra cómo usar la carga diligente para propiedades de navegación dentro de entidades que, a su vez, se encuentran en propiedades de navegación.
  • Cuando el usuario selecciona un curso, se muestran los datos relacionados del conjunto de entidades Enrollments. Las entidades Course y Enrollment se encuentran en una relación uno a varios. Agregarás carga explícita para las entidades Enrollment y sus entidades relacionadas Student. (La carga explícita no es necesaria porque la carga diferida está habilitada, pero esto muestra cómo realizar la carga explícita).

Creación de un modelo de vista para la vista de Índice de instructores

La página Índice de instructores muestra tres tablas diferentes. Por tanto, creará un modelo de vista que incluye tres propiedades, cada una con los datos de una de las tablas.

En la carpeta ViewModels, crea InstructorIndexData.cs y sustituye el código existente por el siguiente código:

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

Agregar un estilo a filas seleccionadas

Para marcar las filas seleccionadas, necesitas un color de fondo diferente. Para proporcionar un estilo para esta interfaz de usuario, agrega el código resaltado siguiente a la sección /* info and errors */ de Content\Site.css, como se muestra a continuación:

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

Creación del controlador de instructor y las vistas

Crea un controlador InstructorController como se muestra en la ilustración siguiente:

Add_Controller_dialog_box_for_Instructor_controller

Abre Controllers\InstructorController.cs y agrega una instrucción using para el espacio de nombres ViewModels:

using ContosoUniversity.ViewModels;

El código con scaffolding en el método Index especifica la carga diligente solo para la propiedad de navegación OfficeAssignment:

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

Reemplaza el método Index por el código siguiente para cargar datos relacionados adicionales y colocarlos en el modelo de vista:

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

El método acepta datos de ruta opcionales (id) y un parámetro de cadena de consulta (courseID) que proporcionan los valores de identificador del instructor seleccionado y el curso seleccionado, y pasa todos los datos necesarios a la vista. Los parámetros se proporcionan mediante los hipervínculos Select de la página.

Sugerencia

Datos de ruta

Los datos de ruta son datos que el enlazador de modelos encontró en un segmento de dirección URL especificado en la tabla de enrutamiento. Por ejemplo, la ruta predeterminada especifica los segmentos de controller, action e id:

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

En la dirección URL siguiente, la ruta predeterminada asigna Instructor como el controller, Index como la action y 1 como el id; estos son los valores de datos de ruta.

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

"?courseID=2021" es un valor de cadena de consulta. El enlazador de modelos también funcionará si pasas el id como valor de cadena de consulta:

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

Las instrucciones ActionLink crean las direcciones URL en la vista de Razor. En el siguiente código, el parámetro id coincide con la ruta predeterminada, por lo que se agrega id a los datos de la ruta.

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

En el siguiente código, courseID no coincide con ningún parámetro de la ruta predeterminada, por lo que se agrega como una cadena de consulta.

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

El código comienza creando una instancia del modelo de vista y coloca en ella la lista de instructores. El código especifica la carga diligente para Instructor.OfficeAssignment y las propiedades de navegación de 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);

El segundo método Include carga Cursos y, para cada Curso que se carga, realiza una carga diligente para la propiedad de navegación Course.Department.

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

Como se mencionó anteriormente, la carga diligente no es necesaria, pero se realiza para mejorar el rendimiento. Como la vista siempre necesita la entidad OfficeAssignment, resulta más eficaz capturarla en la misma consulta. Las entidades Course son necesarias cuando se selecciona un instructor en la página web, por lo que la carga diligente es mejor que la carga diferida solo si la página se muestra más a menudo con un curso seleccionado que sin él.

Si se seleccionó un identificador de instructor, el instructor seleccionado se recupera de la lista de instructores del modelo de vista. Después, se carga la propiedad Courses del modelo de vista con las entidades Course de la propiedad de navegación Courses de ese instructor.

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

El método Where devuelve una colección, pero en este caso los criterios pasados a ese método hacen que sólo se devuelva una única entidad Instructor. El método Single convierte la colección en una única entidad Instructor, lo que proporciona acceso a la propiedad Courses de esa entidad.

Se utiliza el método Único en una colección cuando se sabe que la colección sólo tendrá un elemento. El método Single inicia una excepción si la colección que se pasa está vacía o si hay más de un elemento. Una alternativa es SingleOrDefault, que devuelve un valor predeterminado (null en este caso) si la colección está vacía. Pero en este caso, eso seguiría iniciando una excepción (al tratar de buscar una propiedad Courses en una referencia null), y el mensaje de excepción indicaría con menos claridad la causa del problema. Cuando se llama al método Single, también se puede pasar la condición Where en lugar de llamar al método Where por separado:

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

En lugar de:

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

A continuación, si se ha seleccionado un curso, se recupera de la lista de cursos en el modelo de vista. Después, se carga la propiedad Enrollments del modelo de vista con las entidades Enrollment de la propiedad de navegación Enrollments de ese curso.

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

Modificación de la vista de índice de instructores

En Views/Instructors/Index.cshtml, reemplace el código existente con el código siguiente. Se resaltan los cambios:

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

Ha realizado los cambios siguientes en el código existente:

  • Ha cambiado la clase de modelo por InstructorIndexData.

  • Ha cambiado el título de la página de Index a Instructors.

  • Has movido las columnas del vínculo de fila a la izquierda.

  • Has eliminado la columna FullName.

  • Has agregado una columna Office en la que se muestra item.OfficeAssignment.Location solo si item.OfficeAssignment no es NULL. (Dado que se trata de una relación de uno a cero o de uno a uno, es posible que no exista una entidad OfficeAssignment relacionada).

    <td> 
        @if (item.OfficeAssignment != null) 
        { 
            @item.OfficeAssignment.Location  
        } 
    </td>
    
  • Has agregado código que agrega dinámicamente class="selectedrow" al elemento tr del instructor seleccionado. Esto establece un color de fondo para la fila seleccionada mediante la clase CSS que creaste anteriormente. (El atributo valign será útil en el siguiente tutorial al agregar una columna de varias filas a la tabla).

    string selectedRow = ""; 
    if (item.InstructorID == ViewBag.InstructorID) 
    { 
        selectedRow = "selectedrow"; 
    } 
    <tr class="@selectedRow" valign="top">
    
  • Has agregado un ActionLink nuevo con la etiqueta Seleccionar inmediatamente antes de los otros vínculos de cada fila, lo que hace que el identificador del instructor seleccionado se envíe al método Index.

Ejecuta la aplicación y selecciona la pestaña Instructores. La página muestra la propiedad Location de las entidades relacionadas OfficeAssignment y una celda de tabla vacía cuando no hay ninguna entidad relacionada OfficeAssignment.

Instructors_index_page_with_nothing_selected

En el archivo Views/Instructors/Index.cshtml, después del elemento de tabla de cierre table (situado al final del archivo), agrega el siguiente código resaltado. Este muestra una lista de cursos relacionados con un instructor cuando se selecciona un instructor.

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

Este código lee la propiedad Courses del modelo de vista para mostrar una lista de cursos. También proporciona un hipervínculo Select que envía el ID del curso seleccionado al método de acción Index.

Nota:

Los exploradores almacenan en caché el archivo .css. Si no ves los cambios al ejecutar la aplicación, realiza una actualización completa (mantén presionada la tecla CTRL mientras haces clic en el botón Actualizar o presiona CTRL+F5).

Actualiza la página y selecciona un instructor. Ahora verá una cuadrícula en la que se muestran los cursos asignados al instructor seleccionado, y para cada curso, el nombre del departamento asignado.

Instructors_index_page_with_instructor_selected

Después del bloque de código que se acaba de agregar, agregue el código siguiente. Esto muestra una lista de los estudiantes que están inscritos en un curso cuando se selecciona ese curso.

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

Este código lee la propiedad Enrollments del modelo de vista para mostrar una lista de los alumnos inscritos en el curso.

Actualiza la página y selecciona un instructor. Después, seleccione un curso para ver la lista de los estudiantes inscritos y sus calificaciones.

Screenshot of the Instructors Index page with an instructor and one of their courses selected.

Agregar carga explícita

Abre InstructorController.cs y observa cómo obtiene el método Index la lista de inscripciones de un curso seleccionado:

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

Cuando recuperaste la lista de instructores, especificaste la carga diligente para la propiedad de navegación Courses y para la propiedad Department de cada curso. A continuación, colocas la colección Courses en el modelo de vista y ahora tienes acceso a la propiedad de navegación Enrollments desde una entidad de esa colección. Dado que no especificaste la carga diligente para la propiedad de navegación Course.Enrollments, los datos de esa propiedad aparecen en la página como resultado de la carga diferida.

Si deshabilitaste la carga diferida sin cambiar el código de ninguna otra manera, la propiedad Enrollments sería null independientemente de cuántas inscripciones tuviera el curso. En ese caso, para cargar la propiedad Enrollments, tendrías que especificar la carga diligente o la carga explícita. Ya has visto cómo hacer una carga diligente. Para ver un ejemplo de carga explícita, reemplaza el método Index por el código siguiente, que carga explícitamente la propiedad Enrollments. Los cambios de código aparecen resaltados.

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

Después de obtener la entidad Course seleccionada, el nuevo código carga explícitamente la propiedad de navegación del curso Enrollments:

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

A continuación, carga explícitamente la entidad Student relacionada de cada entidad Enrollment:

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

Ten en cuenta que usas el método Collection para cargar una propiedad de colección, pero para una propiedad que contiene solo una entidad, usas el método Reference. Ya puedes ejecutar la página Índice de instructores y no verás ninguna diferencia en lo que se muestra en la página, aunque hayas cambiado cómo se recuperan los datos.

Resumen

Ya has usado las tres formas (diferida, diligente y explícita) de cargar datos relacionados en las propiedades de navegación. En el siguiente tutorial, obtendrá información sobre cómo actualizar datos relacionados.

En el mapa de contenido de acceso a datos de ASP.NET se pueden encontrar vínculos a otros recursos de Entity Framework.