Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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.
Carga diferida, diligente y explícita de datos relacionados
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.
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
.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étodoReference.Load
para las propiedades que contienen una sola entidad. (En el ejemplo siguiente, si quieres cargar la propiedad de navegación Administrador, debes reemplazarCollection(x => x.Courses)
porReference(x => x.Administrator)
).
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
comofalse
. 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):
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.
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:
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 entidadesInstructor
yOfficeAssignment
se encuentran en una relación de uno a cero o uno. Usará la carga diligente para las entidadesOfficeAssignment
. 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 entidadesInstructor
yCourse
se encuentran en una relación de varios a varios. Usará la carga diligente para las entidadesCourse
y sus entidadesDepartment
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 entidadesCourse
yEnrollment
se encuentran en una relación uno a varios. Agregarás carga explícita para las entidadesEnrollment
y sus entidades relacionadasStudent
. (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:
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 siitem.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 entidadOfficeAssignment
relacionada).<td> @if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location } </td>
Has agregado código que agrega dinámicamente
class="selectedrow"
al elementotr
del instructor seleccionado. Esto establece un color de fondo para la fila seleccionada mediante la clase CSS que creaste anteriormente. (El atributovalign
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étodoIndex
.
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
.
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.
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.
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.