Partilhar via


Lendo dados relacionados com o Entity Framework em um aplicativo MVC ASP.NET (5 de 10)

por Tom Dykstra

O aplicativo Web de exemplo da Contoso University demonstra como criar ASP.NET aplicativos MVC 4 usando o Entity Framework 5 Code First e o Visual Studio 2012. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial da série.

Observação

Se você tiver um problema, não poderá resolve, baixe o capítulo concluído e tente reproduzir o problema. Geralmente, você pode encontrar a solução para o problema comparando seu código com o código concluído. Para obter alguns erros comuns e como resolvê-los, consulte Erros e soluções alternativas.

No tutorial anterior, você concluiu o modelo de dados Da escola. Neste tutorial, você lerá e exibirá dados relacionados, ou seja, dados que o Entity Framework carrega nas propriedades de navegação.

As ilustrações a seguir mostram as páginas com as quais você trabalhará.

Captura de tela que mostra a página Índice de Cursos da Contoso University.

Captura de tela que mostra a página Índice de Instrutores da Contoso University com um instrutor e um de seus cursos selecionados.

Há várias maneiras pelas quais o Entity Framework pode carregar dados relacionados nas propriedades de navegação de uma entidade:

  • Carregamento lento. Quando a entidade é lida pela primeira vez, os dados relacionados não são recuperados. No entanto, na primeira vez que você tenta acessar uma propriedade de navegação, os dados necessários para essa propriedade de navegação são recuperados automaticamente. Isso resulta em várias consultas enviadas para o banco de dados — uma para a própria entidade e outra para cada vez que os dados relacionados para a entidade devem ser recuperados.

    Lazy_loading_example

  • Carregamento adiantado. Quando a entidade é lida, os dados relacionados são recuperados com ela. Normalmente, isso resulta em uma única consulta de junção que recupera todos os dados necessários. Especifique o carregamento ansioso usando o Include método .

    Eager_loading_example

  • Carregamento explícito. Isso é semelhante ao carregamento lento, exceto que você recupera explicitamente os dados relacionados no código; isso não acontece automaticamente quando você acessa uma propriedade de navegação. Você carrega dados relacionados manualmente obtendo a entrada do gerenciador de estado do objeto para uma entidade e chamando o Collection.Load método para coleções ou o Reference.Load método para propriedades que contêm uma única entidade. (No exemplo a seguir, se você quisesse carregar a propriedade de navegação Administrador, substituiria Collection(x => x.Courses) por Reference(x => x.Administrator).)

    Explicit_loading_example

Como eles não recuperam imediatamente os valores de propriedade, o carregamento lento e o carregamento explícito também são conhecidos como carregamento adiado.

Em geral, se você souber que precisa de dados relacionados para cada entidade recuperada, o carregamento ansioso oferece o melhor desempenho, pois uma única consulta enviada ao banco de dados normalmente é mais eficiente do que consultas separadas para cada entidade recuperada. Por exemplo, nos exemplos acima, suponha que cada departamento tenha dez cursos relacionados. O exemplo de carregamento ansioso resultaria em apenas uma única consulta (junção) e uma única viagem de ida e volta ao banco de dados. Os exemplos de carregamento lento e explícito resultariam em onze consultas e onze viagens de ida e volta ao banco de dados. As viagens de ida e volta extras para o banco de dados são especialmente prejudiciais ao desempenho quando a latência é alta.

Por outro lado, em alguns cenários, o carregamento lento é mais eficiente. O carregamento ansioso pode fazer com que uma junção muito complexa seja gerada, o que SQL Server não pode processar com eficiência. Ou se você precisar acessar as propriedades de navegação de uma entidade somente para um subconjunto de um conjunto de entidades que você está processando, o carregamento lento pode ter um desempenho melhor, pois o carregamento ansioso recuperaria mais dados do que você precisa. Se o desempenho for crítico, será melhor testar o desempenho das duas maneiras para fazer a melhor escolha.

Normalmente, você usaria o carregamento explícito somente quando desativasse o carregamento lento. Um cenário em que você deve desativar o carregamento lento é durante a serialização. O carregamento lento e a serialização não se misturam bem e, se você não tiver cuidado, poderá acabar consultando significativamente mais dados do que você pretendia quando o carregamento lento estiver habilitado. A serialização geralmente funciona acessando cada propriedade em uma instância de um tipo. O acesso à propriedade dispara o carregamento lento e essas entidades carregadas lentas são serializadas. Em seguida, o processo de serialização acessa cada propriedade das entidades carregadas lentamente, potencialmente causando carregamento e serialização ainda mais lentos. Para evitar essa reação em cadeia de fuga, desative o carregamento lento antes de serializar uma entidade.

A classe de contexto do banco de dados executa o carregamento lento por padrão. Há duas maneiras de desabilitar o carregamento lento:

  • Para propriedades de navegação específicas, omita o virtual palavra-chave ao declarar a propriedade.

  • Para todas as propriedades de navegação, defina LazyLoadingEnabled como false. Por exemplo, você pode colocar o seguinte código no construtor da classe de contexto:

    this.Configuration.LazyLoadingEnabled = false;
    

O carregamento lento pode mascarar o código que causa problemas de desempenho. Por exemplo, o código que não especifica carregamentos ansiosos ou explícitos, mas processa um alto volume de entidades e usa várias propriedades de navegação em cada iteração pode ser muito ineficiente (devido a muitas viagens de ida e volta ao banco de dados). Um aplicativo que tem um bom desempenho no desenvolvimento usando um SQL Server local pode ter problemas de desempenho quando movido para SQL do Azure Banco de Dados devido ao aumento da latência e ao carregamento lento. A criação de perfil das consultas de banco de dados com uma carga de teste realista ajudará você a determinar se o carregamento lento é apropriado. Para obter mais informações, consulte Desmistificando estratégias do Entity Framework: carregando dados relacionados e usando o Entity Framework para reduzir a latência de rede para SQL Azure.

Criar uma página de índice de cursos que exibe o nome do departamento

A entidade Course inclui uma propriedade de navegação que contém a entidade Department do departamento ao qual o curso é atribuído. Para exibir o nome do departamento atribuído em uma lista de cursos, você precisa obter a Name propriedade da Department entidade que está na Course.Department propriedade de navegação.

Crie um controlador chamado CourseController para o Course tipo de entidade, usando as mesmas opções que você fez anteriormente para o Student controlador, conforme mostrado na ilustração a seguir (exceto ao contrário da imagem, sua classe de contexto está no namespace da DAL, não no namespace Modelos):

Add_Controller_dialog_box_for_Course_controller

Abra Controllers\CourseController.cs e examine o Index método :

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

O scaffolding automático especificou o carregamento adiantado para a propriedade de navegação Department usando o método Include.

Abra Views\Course\Index.cshtml e substitua o código existente pelo código a seguir. As alterações são realçadas:

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

Você fez as seguintes alterações no código gerado por scaffolding:

  • O cabeçalho mudou de Índice para Cursos.
  • Moveu os links de linha para a esquerda.
  • Adicionada uma coluna sob o título Número que mostra o valor da CourseID propriedade. (Por padrão, as chaves primárias não são scaffolded porque normalmente não têm sentido para os usuários finais. No entanto, nesse caso, a chave primária é significativa e você deseja mostrá-la.)
  • Alterou o título da última coluna de DepartmentID (o nome da chave estrangeira para a Department entidade) para Departamento.

Observe que, para a última coluna, o código scaffolded exibe a Name propriedade da Department entidade carregada na Department propriedade de navegação:

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

Execute a página (selecione a guia Cursos na home page da Contoso University) para ver a lista com nomes de departamento.

Courses_index_page_with_department_names

Criar uma página de índice de instrutores que mostra cursos e inscrições

Nesta seção, você criará um controlador e uma exibição para a Instructor entidade para exibir a página Índice de Instrutores:

Captura de tela mostrando a página Índice de Instrutores com um instrutor e um de seus cursos selecionados.

Essa página lê e exibe dados relacionados das seguintes maneiras:

  • A lista de instrutores exibe dados relacionados da entidade OfficeAssignment. As entidades Instructor e OfficeAssignment estão em uma relação um para zero ou um. Você usará o carregamento adiantado para as entidades OfficeAssignment. Conforme explicado anteriormente, o carregamento adiantado é geralmente mais eficiente quando você precisa dos dados relacionados para todas as linhas recuperadas da tabela primária. Nesse caso, você deseja exibir atribuições de escritório para todos os instrutores exibidos.
  • Quando o usuário seleciona um instrutor, as entidades Course relacionadas são exibidas. As entidades Instructor e Course estão em uma relação muitos para muitos. O carregamento adiantado é usado para as entidades Course e suas entidades Department relacionadas. Nesse caso, o carregamento lento pode ser mais eficiente porque você precisa de cursos apenas para o instrutor selecionado. No entanto, este exemplo mostra como usar o carregamento adiantado para propriedades de navegação em entidades que estão nas propriedades de navegação.
  • Quando o usuário seleciona um curso, os dados relacionados do conjunto de entidades Enrollments são exibidos. As entidades Course e Enrollment estão em uma relação um-para-muitos. Você adicionará o carregamento explícito para Enrollment entidades e suas entidades relacionadas Student . (O carregamento explícito não é necessário porque o carregamento lento está habilitado, mas isso mostra como fazer o carregamento explícito.)

Criar um modelo de exibição para a exibição de índice de instrutor

A página Índice de Instrutor mostra três tabelas diferentes. Portanto, você criará um modelo de exibição que inclui três propriedades, cada uma contendo os dados de uma das tabelas.

Na pasta ViewModels , crie InstructorIndexData.cs e substitua o código existente pelo seguinte 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; }
    }
}

Adicionando um estilo para linhas selecionadas

Para marcar as linhas selecionadas, você precisa de uma cor de plano de fundo diferente. Para fornecer um estilo para essa interface do usuário, adicione o seguinte código realçado à seção /* info and errors */ em Content\Site.css, conforme mostrado abaixo:

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

Criando o controlador e as exibições do instrutor

Crie um InstructorController controlador, conforme mostrado na ilustração a seguir:

Add_Controller_dialog_box_for_Instructor_controller

Abra Controllers\InstructorController.cs e adicione uma using instrução para o ViewModels namespace:

using ContosoUniversity.ViewModels;

O código scaffolded no método especifica o Index carregamento adiantado somente para a OfficeAssignment propriedade de navegação:

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

Substitua o Index método pelo seguinte código para carregar dados relacionados adicionais e colocá-lo no modelo de exibição:

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

O método aceita dados de rota opcionais (id) e um parâmetro de cadeia de caracteres de consulta (courseID) que fornecem os valores de ID do instrutor selecionado e do curso selecionado e passa todos os dados necessários para a exibição. Os parâmetros são fornecidos pelos hiperlinks Selecionar na página.

Dica

Rotear dados

Dados de rota são dados que o associador de modelo encontrou em um segmento de URL especificado na tabela de roteamento. Por exemplo, a rota padrão especifica controlleros segmentos , actione id :

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

Na URL a seguir, a rota padrão é mapeada Instructor como , controllerIndex como e action 1 como ; idesses são valores de dados de rota.

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

"?courseID=2021" é um valor de cadeia de caracteres de consulta. O associador de modelo também funcionará se você passar o id como um valor de cadeia de caracteres de consulta:

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

As URLs são criadas por ActionLink instruções no modo de exibição Razor. No código a seguir, o id parâmetro corresponde à rota padrão, portanto id , é adicionado aos dados de rota.

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

No código a seguir, courseID não corresponde a um parâmetro na rota padrão, portanto, ele é adicionado como uma cadeia de caracteres de consulta.

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

O código começa com a criação de uma instância do modelo de exibição e colocando-a na lista de instrutores. O código especifica o carregamento adiantado para a Instructor.OfficeAssignment propriedade de navegação e 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);

O segundo Include método carrega Cursos e, para cada Curso carregado, ele faz o carregamento adiantado para a Course.Department propriedade de navegação.

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

Conforme mencionado anteriormente, o carregamento adiantado não é necessário, mas é feito para melhorar o desempenho. Como a exibição sempre exige a entidade OfficeAssignment, é mais eficiente buscar isso na mesma consulta. Course as entidades são necessárias quando um instrutor é selecionado na página da Web, portanto, o carregamento adiantado é melhor do que o carregamento lento somente se a página for exibida com mais frequência com um curso selecionado do que sem.

Se uma ID de instrutor tiver sido selecionada, o instrutor selecionado será recuperado da lista de instrutores no modelo de exibição. Em seguida, a propriedade Courses do modelo de exibição é carregada com as entidades Course da propriedade de navegação Courses desse instrutor.

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

O Where método retorna uma coleção, mas nesse caso os critérios passados para esse método resultam em apenas uma única Instructor entidade sendo retornada. O método Single converte a coleção em uma única entidade Instructor, que fornece acesso à propriedade Courses dessa entidade.

Use o método Single em uma coleção quando souber que a coleção terá apenas um item. O Single método gera uma exceção se a coleção passada para ela estiver vazia ou se houver mais de um item. Uma alternativa é SingleOrDefault, que retorna um valor padrão (null nesse caso) se a coleção estiver vazia. No entanto, nesse caso, isso ainda resultaria em uma exceção (da tentativa de localizar uma Courses propriedade em uma null referência) e a mensagem de exceção indicaria menos claramente a causa do problema. Ao chamar o Single método , você também pode passar a Where condição em vez de chamar o Where método separadamente:

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

Em vez de:

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

Em seguida, se um curso foi selecionado, o curso selecionado é recuperado na lista de cursos no modelo de exibição. Em seguida, a propriedade do modelo de Enrollments exibição é carregada com as Enrollment entidades da propriedade de navegação desse Enrollments curso.

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

Modificando a exibição de índice do instrutor

Em Views\Instructor\Index.cshtml, substitua o código existente pelo código a seguir. As alterações são realçadas:

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

Você fez as seguintes alterações no código existente:

  • Alterou a classe de modelo para InstructorIndexData.

  • Alterou o título de página de Índice para Instrutores.

  • Moveu as colunas de link de linha para a esquerda.

  • A coluna FullName foi removida.

  • Adicionada uma coluna do Office que será item.OfficeAssignment.Location exibida somente se item.OfficeAssignment não for nula. (Como essa é uma relação de um para zero ou um, pode não haver uma entidade relacionada OfficeAssignment .)

    <td> 
        @if (item.OfficeAssignment != null) 
        { 
            @item.OfficeAssignment.Location  
        } 
    </td>
    
  • Código adicionado que adicionará dinamicamente class="selectedrow" ao tr elemento do instrutor selecionado. Isso define uma cor da tela de fundo para a linha selecionada usando a classe CSS que você criou anteriormente. (O valign atributo será útil no tutorial a seguir quando você adicionar uma coluna de várias linhas à tabela.)

    string selectedRow = ""; 
    if (item.InstructorID == ViewBag.InstructorID) 
    { 
        selectedRow = "selectedrow"; 
    } 
    <tr class="@selectedRow" valign="top">
    
  • Adicionada uma nova ActionLink rotulada Selecionar imediatamente antes dos outros links em cada linha, o que faz com que a ID do instrutor selecionada seja enviada para o Index método .

Execute o aplicativo e selecione a guia Instrutores . A página exibe a Location propriedade de entidades relacionadas OfficeAssignment e uma célula de tabela vazia quando não há nenhuma entidade relacionada OfficeAssignment .

Instructors_index_page_with_nothing_selected

No arquivo Views\Instructor\Index.cshtml , após o elemento de fechamento table (no final do arquivo), adicione o código realçado a seguir. Isso exibe uma lista de cursos relacionados a um instrutor quando um instrutor é selecionado.

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

Esse código lê a propriedade Courses do modelo de exibição para exibir uma lista de cursos. Ele também fornece um Select hiperlink que envia a ID do curso selecionado para o método de Index ação.

Observação

O arquivo .css é armazenado em cache por navegadores. Se você não vir as alterações ao executar o aplicativo, faça uma atualização rígida (mantenha pressionada a tecla CTRL ao clicar no botão Atualizar ou pressione CTRL+F5).

Execute a página e selecione um instrutor. Agora, você verá uma grade que exibe os cursos atribuídos ao instrutor selecionado, e para cada curso, verá o nome do departamento atribuído.

Instructors_index_page_with_instructor_selected

Após o bloco de código que você acabou de adicionar, adicione o código a seguir. Isso exibe uma lista dos alunos que estão registrados em um curso quando esse curso é selecionado.

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

Esse código lê a propriedade Enrollments do modelo de exibição para exibir uma lista dos alunos matriculados no curso.

Execute a página e selecione um instrutor. Em seguida, selecione um curso para ver a lista de alunos registrados e suas notas.

Captura de tela da página Índice de Instrutores com um instrutor e um de seus cursos selecionados.

Adicionando carregamento explícito

Abra InstructorController.cs e veja como o Index método obtém a lista de registros de um curso selecionado:

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

Ao recuperar a lista de instrutores, você especificou o carregamento adiantado para a Courses propriedade de navegação e para a Department propriedade de cada curso. Em seguida, você coloca a Courses coleção no modelo de exibição e agora está acessando a Enrollments propriedade de navegação de uma entidade nessa coleção. Como você não especificou o carregamento adiantado para a Course.Enrollments propriedade de navegação, os dados dessa propriedade são exibidos na página como resultado do carregamento lento.

Se você desabilitou o carregamento lento sem alterar o código de outra forma, a Enrollments propriedade seria nula, independentemente de quantos registros o curso realmente tinha. Nesse caso, para carregar a Enrollments propriedade, você teria que especificar carregamento adiantado ou carregamento explícito. Você já viu como fazer carregamento adiantado. Para ver um exemplo de carregamento explícito, substitua o Index método pelo código a seguir, que carrega explicitamente a Enrollments propriedade . O código alterado está realçado.

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

Depois de obter a entidade selecionada Course , o novo código carrega explicitamente a propriedade de navegação do Enrollments curso:

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

Em seguida, ele carrega explicitamente a entidade relacionada Student de cada Enrollment entidade:

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

Observe que você usa o Collection método para carregar uma propriedade de coleção, mas para uma propriedade que contém apenas uma entidade, use o Reference método . Você pode executar a página Índice de Instrutor agora e não verá nenhuma diferença no que é exibido na página, embora tenha alterado a forma como os dados são recuperados.

Resumo

Agora você usou todas as três maneiras (lentas, ansiosas e explícitas) para carregar dados relacionados em propriedades de navegação. No próximo tutorial, você aprenderá a atualizar dados relacionados.

Os links para outros recursos do Entity Framework podem ser encontrados no Mapa de Conteúdo de Acesso a Dados do ASP.NET.