Compartilhar via


Tutorial - ASP.NET MVC com EF Core

No tutorial anterior, você concluiu o modelo de dados Escola. Neste tutorial, você lerá e exibirá dados relacionados – ou seja, os 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á.

Courses Index page

Instructors Index page

Neste tutorial, você:

  • Aprender a carregar entidades relacionadas
  • Criar uma página Cursos
  • Criar uma página Instrutores
  • Aprender sobre o carregamento explícito

Pré-requisitos

Há várias maneiras pelas quais um software ORM (Object-Relational Mapping), como o Entity Framework, pode carregar dados relacionados nas propriedades de navegação de uma entidade:

  • 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 adiantado no Entity Framework Core usando os métodos Include e ThenInclude.

    Eager loading example

    Recupere alguns dos dados em consultas separadas e o EF "corrigirá" as propriedades de navegação. Ou seja, o EF adiciona de forma automática as entidades recuperadas separadamente no local em que pertencem nas propriedades de navegação de entidades recuperadas anteriormente. Para a consulta que recupera dados relacionados, você pode usar o método Load em vez de um método que retorna uma lista ou um objeto, como ToList ou Single.

    Separate queries example

  • Carregamento explícito: Quando a entidade é lida pela primeira vez, os dados relacionados não são recuperados. Você escreve o código que recupera os dados relacionados se eles são necessários. Como no caso do carregamento adiantado com consultas separadas, o carregamento explícito resulta no envio de várias consultas ao banco de dados. A diferença é que, com o carregamento explícito, o código especifica as propriedades de navegação a serem carregadas. No Entity Framework Core 1.1, você pode usar o método Load para fazer o carregamento explícito. Por exemplo:

    Explicit loading example

  • Carregamento adiado: 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. Uma consulta é enviada ao banco de dados sempre que você tenta obter dados de uma propriedade de navegação pela primeira vez. O Entity Framework Core 1.0 não dá suporte ao carregamento lento.

Considerações sobre o desempenho

Se você sabe que precisa de dados relacionados para cada entidade recuperada, o carregamento adiantado costuma oferecer o melhor desempenho, porque uma única consulta enviada para o banco de dados é geralmente mais eficiente do que consultas separadas para cada entidade recuperada. Por exemplo, suponha que cada departamento tenha dez cursos relacionados. O carregamento adiantado de todos os dados relacionados resultará em apenas uma única consulta (junção) e uma única viagem de ida e volta para o banco de dados. Uma consulta separada para cursos de cada departamento resultará em onze viagens de ida e volta para o 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, consultas separadas são mais eficientes. O carregamento adiantado de todos os dados relacionados em uma consulta pode fazer com que uma junção muito complexa seja gerada, que o SQL Server não consegue processar com eficiência. Ou se precisar acessar as propriedades de navegação de uma entidade somente para um subconjunto de um conjunto de entidades que está sendo processado, consultas separadas poderão ter um melhor desempenho, pois o carregamento adiantado de tudo desde o início recupera 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.

Criar uma página Cursos

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 propriedade Name da entidade Department que está na propriedade de navegação Course.Department.

Crie um controlador chamado CoursesController para o tipo de entidade Course, usando as mesmas opções para o Controlador MVC com exibições, usando o scaffolder do Entity Framework que você usou anteriormente para StudentsController, conforme mostrado na seguinte ilustração:

Add Courses controller

Abra CoursesController.cs e examine o arquivo Index. O scaffolding automático especificou o carregamento adiantado para a propriedade de navegação Department usando o método Include.

Substitua o método Index pelo seguinte código, que usa um nome mais apropriado para o IQueryable que retorna as entidades Course (courses em vez de schoolContext):

public async Task<IActionResult> Index()
{
    var courses = _context.Courses
        .Include(c => c.Department)
        .AsNoTracking();
    return View(await courses.ToListAsync());
}

Abra Views/Courses/Index.cshtml e substitua o código do modelo pelo seguinte código. As alterações são realçadas:

@model IEnumerable<ContosoUniversity.Models.Course>

@{
    ViewData["Title"] = "Courses";
}

<h2>Courses</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.CourseID)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Credits)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Department)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.CourseID)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Title)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Credits)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Department.Name)
                </td>
                <td>
                    <a asp-action="Edit" asp-route-id="@item.CourseID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.CourseID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

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

  • O cabeçalho mudou de Índice para Cursos.

  • Adicionou uma coluna Número que mostra o valor da propriedade CourseID. Por padrão, as chaves primárias não são geradas por scaffolding 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 a coluna Departamento para que ela exiba o nome de departamento. O código exibe a propriedade Name da entidade Department que é carregada na propriedade de navegação Department:

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

Execute o aplicativo e selecione a guia Cursos para ver a lista com nomes de departamentos.

Courses Index page

Criar uma página Instrutores

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

Instructors Index page

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, consultas separadas podem ser mais eficientes porque você precisa de cursos somente 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ê usará consultas separadas para entidades Enrollment e suas entidades Student relacionadas.

Criar um modelo de exibição para a exibição Índice de Instrutor

A página Instrutores mostra dados de 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 SchoolViewModels, crie InstructorIndexData.cs e substitua o código existente pelo seguinte código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Models.SchoolViewModels
{
    public class InstructorIndexData
    {
        public IEnumerable<Instructor> Instructors { get; set; }
        public IEnumerable<Course> Courses { get; set; }
        public IEnumerable<Enrollment> Enrollments { get; set; }
    }
}

Criar exibições e o controlador Instrutor

Crie um controlador Instrutores com ações de leitura/gravação do EF, conforme mostrado na seguinte ilustração:

Add Instructors controller

Abra InstructorsController.cs e adicione uma declaração using no namespace ViewModels:

using ContosoUniversity.Models.SchoolViewModels;

Substitua o método Index pelo código a seguir para fazer o carregamento adiantado de dados relacionados e colocá-los no modelo de exibição.

public async Task<IActionResult> Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();
    viewModel.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Enrollments)
                    .ThenInclude(i => i.Student)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
          .AsNoTracking()
          .OrderBy(i => i.LastName)
          .ToListAsync();
    
    if (id != null)
    {
        ViewData["InstructorID"] = id.Value;
        Instructor instructor = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single();
        viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
    }

    if (courseID != null)
    {
        ViewData["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 fornece os valores de ID do curso e do instrutor selecionados. Os parâmetros são fornecidos pelos hiperlinks Selecionar na página.

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 as propriedades de navegação Instructor.OfficeAssignment e Instructor.CourseAssignments. Dentro da propriedade CourseAssignments, a propriedade Course é carregada e, dentro dela, as propriedades Enrollments e Department são carregadas e, dentro de cada entidade Enrollment, a propriedade Student é carregada.

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

Como a exibição sempre exige a entidade OfficeAssignment, é mais eficiente buscar isso na mesma consulta. As entidades Course são necessárias quando um instrutor é selecionado na página da Web; portanto, uma única consulta é melhor do que várias consultas apenas se a página é exibida com mais frequência com um curso selecionado do que sem ele.

O código repete CourseAssignments e Course porque você precisa de duas propriedades de Course. A primeira cadeia de caracteres de chamadas ThenInclude obtém CourseAssignment.Course, Course.Enrollments e Enrollment.Student.

Você pode ler mais sobre como incluir vários níveis de dados relacionados aqui.

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

Nesse ponto do código, outro ThenInclude se refere às propriedades de navegação de Student, que não é necessário. Mas a chamada a Include é reiniciada com propriedades Instructor e, portanto, você precisa passar pela cadeia novamente, dessa vez, especificando Course.Department em vez de Course.Enrollments.

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

O código a seguir é executado quando o instrutor é selecionado. O instrutor selecionado é 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 CourseAssignments desse instrutor.

if (id != null)
{
    ViewData["InstructorID"] = id.Value;
    Instructor instructor = viewModel.Instructors.Where(
        i => i.ID == id.Value).Single();
    viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

O método Where retorna uma coleção, mas nesse caso, os critérios passado para esse método resultam no retorno de apenas uma única entidade Instructor. O método Single converte a coleção em uma única entidade Instructor, que fornece acesso à propriedade CourseAssignments dessa entidade. A propriedade CourseAssignments contém entidades CourseAssignment, das quais você deseja apenas entidades Course relacionadas.

Use o método Single em uma coleção quando souber que a coleção terá apenas um item. O método Single gera uma exceção se a coleção passada para ele está vazia ou se há mais de um item. Uma alternativa é SingleOrDefault, que retorna um valor padrão (nulo, nesse caso) se a coleção está vazia. No entanto, nesse caso, isso ainda resultará em uma exceção (da tentativa de encontrar uma propriedade Courses em uma referência nula), e a mensagem de exceção menos claramente indicará a causa do problema. Quando você chama o método Single, também pode passar a condição Where, em vez de chamar o método Where separadamente:

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

Em vez de:

.Where(i => i.ID == 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 Enrollments do modelo de exibição é carregada com as entidades Enrollment da propriedade de navegação Enrollments desse curso.

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

Com acompanhamento versus sem acompanhamento

As consultas sem acompanhamento são úteis quando os resultados são usados em um cenário de somente leitura. Geralmente, eles são mais rápidos para executar porque não há necessidade de configurar as informações de controle de alterações. Se as entidades recuperadas do banco de dados não precisarem ser atualizadas, é provável que uma consulta sem acompanhamento tenha um desempenho melhor do que uma consulta de acompanhamento.

Em alguns casos, uma consulta de acompanhamento é mais eficiente do que uma consulta sem acompanhamento. Para obter mais informações, consulte Comparação entre consultas com e sem acompanhamento.

Modificar a exibição Índice de Instrutor

Em Views/Instructors/Index.cshtml, substitua o código do modelo pelo seguinte código. As alterações são realçadas.

@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

@{
    ViewData["Title"] = "Instructors";
}

<h2>Instructors</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>Last Name</th>
            <th>First Name</th>
            <th>Hire Date</th>
            <th>Office</th>
            <th>Courses</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Instructors)
        {
            string selectedRow = "";
            if (item.ID == (int?)ViewData["InstructorID"])
            {
                selectedRow = "table-success";
            }
            <tr class="@selectedRow">
                <td>
                    @Html.DisplayFor(modelItem => item.LastName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FirstMidName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.HireDate)
                </td>
                <td>
                    @if (item.OfficeAssignment != null)
                    {
                        @item.OfficeAssignment.Location
                    }
                </td>
                <td>
                    @foreach (var course in item.CourseAssignments)
                    {
                        @course.Course.CourseID @course.Course.Title <br />
                    }
                </td>
                <td>
                    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
           }
    </tbody>
</table>
@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

@{
    ViewData["Title"] = "Instructors";
}

<h2>Instructors</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>Last Name</th>
            <th>First Name</th>
            <th>Hire Date</th>
            <th>Office</th>
            <th>Courses</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Instructors)
        {
            string selectedRow = "";
            if (item.ID == (int?)ViewData["InstructorID"])
            {
                selectedRow = "success";
            }
            <tr class="@selectedRow">
                <td>
                    @Html.DisplayFor(modelItem => item.LastName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FirstMidName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.HireDate)
                </td>
                <td>
                    @if (item.OfficeAssignment != null)
                    {
                        @item.OfficeAssignment.Location
                    }
                </td>
                <td>
                    @foreach (var course in item.CourseAssignments)
                    {
                        @course.Course.CourseID @course.Course.Title <br />
                    }
                </td>
                <td>
                    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
           }
    </tbody>
</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.

  • Adicionou uma coluna Office que exibe item.OfficeAssignment.Location somente se item.OfficeAssignment não é nulo. (Como essa é uma relação um para zero ou um, pode não haver uma entidade OfficeAssignment relacionada.)

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Adicionou uma coluna Courses que exibe os cursos ministrados por cada instrutor. Para obter mais informações, consulte a seção Transição de linha explícita no artigo sobre a sintaxe de Razor.

  • Código incluso que adiciona condicionalmente uma classe CSS Bootstrap ao elemento tr do instrutor selecionado. Essa classe define uma cor da tela de fundo para a linha selecionada.

  • Adicionou um novo hiperlink rotulado Selecionar imediatamente antes dos outros links em cada linha, o que faz com que a ID do instrutor selecionado seja enviada para o método Index.

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    

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

Instructors Index page nothing selected

No arquivo Views/Instructors/Index.cshtml, após o elemento de tabela de fechamento (ao final do arquivo), adicione o código a seguir. Esse código exibe uma lista de cursos relacionados a um instrutor quando um instrutor é selecionado.


@if (Model.Courses != null)
{
    <h3>Courses Taught by Selected Instructor</h3>
    <table class="table">
        <tr>
            <th></th>
            <th>Number</th>
            <th>Title</th>
            <th>Department</th>
        </tr>

        @foreach (var item in Model.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == (int?)ViewData["CourseID"])
            {
                selectedRow = "success";
            }
            <tr class="@selectedRow">
                <td>
                    @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr>
        }

    </table>
}

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

Atualize 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 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 class="table">
        <tr>
            <th>Name</th>
            <th>Grade</th>
        </tr>
        @foreach (var item in Model.Enrollments)
        {
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
}

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

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

Instructors Index page instructor and course selected

Sobre o carregamento explícito

Quando você recuperou a lista de instrutores em InstructorsController.cs, você especificou o carregamento adiantado para a propriedade de navegação CourseAssignments.

Suponha que os usuários esperados raramente desejem ver registros em um curso e um instrutor selecionados. Nesse caso, talvez você deseje carregar os dados de registro somente se eles forem solicitados. Para ver um exemplo de como fazer carregamento explícito, substitua o método Index pelo código a seguir, que remove o carregamento adiantado em Enrollments e carrega essa propriedade de forma explícita. As alterações de código são realçadas.

public async Task<IActionResult> Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();
    viewModel.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
          .OrderBy(i => i.LastName)
          .ToListAsync();

    if (id != null)
    {
        ViewData["InstructorID"] = id.Value;
        Instructor instructor = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single();
        viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
    }

    if (courseID != null)
    {
        ViewData["CourseID"] = courseID.Value;
        var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
        await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
        foreach (Enrollment enrollment in selectedCourse.Enrollments)
        {
            await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
        }
        viewModel.Enrollments = selectedCourse.Enrollments;
    }

    return View(viewModel);
}

O novo código remove as chamadas do método ThenInclude dos dados de matrícula do código que recupera as entidades do instrutor. Também solta AsNoTracking. Se um curso e um instrutor são selecionados, o código realçado recupera as entidades Enrollment do curso selecionado e as entidades Student para cada Enrollment.

Execute que o aplicativo, acesse a página Índice de Instrutores agora e você não verá nenhuma diferença no que é exibido na página, embora você tenha alterado a maneira como os dados são recuperados.

Obter o código

Baixe ou exiba o aplicativo concluído.

Próximas etapas

Neste tutorial, você:

  • Aprendeu a carregar dados relacionados
  • Criou uma página Cursos
  • Criou uma página Instrutores
  • Aprendeu sobre o carregamento explícito

Vá para o próximo tutorial para aprender a atualizar dados relacionados.