Tutorial: Ler dados relacionados com o EF em um aplicativo MVC do ASP.NET
No tutorial anterior, você concluiu o modelo de dados 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á.
O aplicativo Web de exemplo da Contoso University demonstra como criar aplicativos MVC 5 ASP.NET usando o Entity Framework 6 Code First e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial da série.
Neste tutorial, você:
- Aprender a carregar entidades relacionadas
- Criar uma página Cursos
- Criar uma página Instrutores
Pré-requisitos
Aprender a carregar entidades relacionadas
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 ao banco de dados — uma para a própria entidade e outra sempre que os dados relacionados à entidade devem ser recuperados. A
DbContext
classe habilita o carregamento lento por padrão.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. Você especifica o carregamento adiantado usando o
Include
método.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 os dados relacionados manualmente obtendo a entrada do gerenciador de estado do objeto para uma entidade e chamando o método Collection.Load para coleções ou o método Reference.Load 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)
porReference(x => x.Administrator)
.) Normalmente, você usaria o carregamento explícito somente quando desativasse o carregamento lento.
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.
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, 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 para o banco de dados. Os exemplos de carregamento lento e carregamento 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 adiantado pode fazer com que uma junção muito complexa seja gerada, que o SQL Server não pode processar com eficiência. Ou, se você precisar acessar as propriedades de navegação de uma entidade apenas para um subconjunto de um conjunto das entidades que você está processando, o carregamento lento poderá ter um desempenho melhor porque o carregamento adiantado recuperaria mais dados do que o necessário. Se o desempenho for crítico, será melhor testar o desempenho das duas maneiras para fazer a melhor escolha.
O carregamento lento pode mascarar o código que causa problemas de desempenho. Por exemplo, o código que não especifica o carregamento ansioso ou explícito, mas processa um grande 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 o Banco de Dados SQL do Azure 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 o SQL Azure.
Desabilitar o carregamento lento antes da serialização
Se você deixar o carregamento lento habilitado durante a serialização, poderá acabar consultando significativamente mais dados do que o pretendido. 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 lentamente são serializadas. Em seguida, o processo de serialização acessa cada propriedade das entidades carregadas lentamente, potencialmente causando ainda mais carregamento e serialização lentos. Para evitar essa reação em cadeia descontrolada, desative o carregamento lento antes de serializar uma entidade.
A serialização também pode ser complicada pelas classes de proxy que o Entity Framework usa, conforme explicado no tutorial Cenários Avançados.
Uma maneira de evitar problemas de serialização é serializar DTOs (objetos de transferência de dados) em vez de objetos de entidade, conforme mostrado no tutorial Usando a API Web com o Entity Framework .
Se você não usar DTOs, poderá desabilitar o carregamento lento e evitar problemas de proxy desabilitando a criação de proxy.
Aqui estão algumas outras maneiras de desativar o carregamento lento:
Para propriedades de navegação específicas, omita a
virtual
palavra-chave ao declarar a propriedade.Para todas as propriedades de navegação, defina
LazyLoadingEnabled
comofalse
, coloque o seguinte código no construtor da classe de contexto:this.Configuration.LazyLoadingEnabled = false;
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 Name
propriedade da Department
entidade que está na Course.Department
propriedade de navegação.
Crie um controlador chamado CourseController
(não CoursesController) para o Course
tipo de entidade, usando as mesmas opções para o Controlador MVC 5 com exibições, usando o scaffolder do Entity Framework que você fez anteriormente para o Student
controlador:
Configuração | Valor |
---|---|
Classe de modelo | Selecione Curso (ContosoUniversity.Models). |
Classe de contexto de dados | Selecione SchoolContext (ContosoUniversity.DAL). |
Nome do controlador | Digite CourseController. Novamente, não CoursesController com um s. Quando você selecionou Curso (ContosoUniversity.Models), o valor do nome do controlador foi preenchido automaticamente. Você tem que mudar o valor. |
Deixe os outros valores padrão e adicione o controlador.
Abra Controllers\CourseController.cs e observe o Index
método:
public ActionResult 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 do modelo 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 class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Credits)
</th>
<th>
Department
</th>
<th></th>
</tr>
@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>
@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>
</tr>
}
</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 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. - Moveu a coluna Departamento para o lado direito e mudou seu título. O scaffolder escolheu corretamente exibir a
Name
propriedade da entidade, mas aqui na página Curso o título da coluna deve ser Departamento em vez deDepartment
Nome.
Observe que, para a coluna Department, o código scaffolded exibe a Name
Department
propriedade da 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.
Criar uma página Instrutores
Nesta seção, você criará um controlador e uma exibição para a Instructor
entidade para exibir a página Instrutores. Essa página lê e exibe dados relacionados das seguintes maneiras:
- A lista de instrutores exibe dados relacionados da entidade
OfficeAssignment
. As entidadesInstructor
eOfficeAssignment
estão em uma relação um para zero ou um. Você usará o carregamento adiantado para as entidadesOfficeAssignment
. 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 entidadesInstructor
eCourse
estão em uma relação muitos para muitos. O carregamento adiantado é usado para as entidadesCourse
e suas entidadesDepartment
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 entidadesCourse
eEnrollment
estão em uma relação um-para-muitos. Você adicionará carregamento explícito paraEnrollment
entidades e suas entidades relacionadasStudent
. (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 do instrutor
A página Instrutores 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; }
}
}
Criar o controlador do instrutor e as exibições
Crie um controlador (não InstructorsController) com a InstructorController
ação de leitura/gravação do EF:
Configuração | Valor |
---|---|
Classe de modelo | Selecione Instrutor (ContosoUniversity.Models). |
Classe de contexto de dados | Selecione SchoolContext (ContosoUniversity.DAL). |
Nome do controlador | Digite InstructorController. Novamente, não InstructorsController com um s. Quando você selecionou Curso (ContosoUniversity.Models), o valor do nome do controlador foi preenchido automaticamente. Você tem que mudar o valor. |
Deixe os outros valores padrão e adicione o controlador.
Abra Controllers\InstructorController.cs e adicione uma using
instrução para o ViewModels
namespace:
using ContosoUniversity.ViewModels;
O código scaffolded no Index
método especifica o carregamento ansioso apenas para a OfficeAssignment
propriedade de navegação:
public ActionResult 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á-los 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.ID == 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 string de consulta (courseID
) que fornecem os valores de ID do instrutor selecionado e do curso selecionado e transmite todos os dados necessários para a exibição. 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 ansioso para a Instructor.OfficeAssignment
propriedade e a Instructor.Courses
propriedade de navegação.
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 Courses e, para cada Course carregado, ele faz o carregamento ansioso para a Course.Department
propriedade de navegação.
.Include(i => i.Courses.Select(c => c.Department))
Como mencionado anteriormente, o carregamento ansioso 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 ansioso é 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.ID == 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.
Você usa o método Single em uma coleção quando sabe que a coleção terá apenas um item. O Single
método gerará uma exceção se a coleção passada para ele 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 (de tentar encontrar 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.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 do modelo de Enrollments
exibição é carregada com as Enrollment
entidades da Enrollments
propriedade de navegação desse curso.
if (courseID != null)
{
ViewBag.CourseID = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
Modificar a exibição do índice do instrutor
Em Views\Instructor\Index.cshtml, substitua o código do modelo 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 class="table">
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th></th>
</tr>
@foreach (var item in Model.Instructors)
{
string selectedRow = "";
if (item.ID == ViewBag.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>
@Html.ActionLink("Select", "Index", new { id = item.ID }) |
@Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
@Html.ActionLink("Details", "Details", new { id = item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id = item.ID })
</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.
Adicionada uma coluna do Office que é exibida
item.OfficeAssignment.Location
somente seitem.OfficeAssignment
não for nula. (Como essa é uma relação de um para zero ou um, pode não haver uma entidade relacionadaOfficeAssignment
.)<td> @if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location } </td>
Adicionado código que será adicionado
class="success"
dinamicamente aotr
elemento do instrutor selecionado. Isso define uma cor da tela de fundo para a linha selecionada usando uma classe Bootstrap.string selectedRow = ""; if (item.InstructorID == ViewBag.InstructorID) { selectedRow = "success"; } <tr class="@selectedRow" valign="top">
Adicionado um novo
ActionLink
rotulado Selecionar imediatamente antes dos outros links em cada linha, o que faz com que a ID do instrutor selecionado seja enviada para oIndex
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
.
No arquivo Views\Instructor\Index.cshtml, após o elemento de fechamento table
(no 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 == ViewBag.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. Ele também fornece um Select
hiperlink que envia o ID do curso selecionado para o Index
método de ação.
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.
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.
Execute a página e selecione um instrutor. Em seguida, selecione um curso para ver a lista de alunos registrados e suas notas.
Adicionando carregamento explícito
Abra InstructorController.cs e veja como o Index
método obtém a lista de matrículas 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 ansioso 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 ansioso para a Course.Enrollments
propriedade de navegação, os dados dessa propriedade estão aparecendo na página como resultado do carregamento lento.
Se você desabilitasse o carregamento lento sem alterar o código de qualquer outra forma, a Enrollments
propriedade seria nula, independentemente de quantas matrículas o curso realmente tivesse. Nesse caso, para carregar a Enrollments
propriedade, você teria que especificar o carregamento ansioso ou o carregamento explícito. Você já viu como fazer o carregamento ansioso. 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 é destacado.
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.ID == id.Value).Single().Courses;
}
if (courseID != null)
{
ViewBag.CourseID = courseID.Value;
// Lazy loading
//viewModel.Enrollments = viewModel.Courses.Where(
// x => x.CourseID == courseID).Single().Enrollments;
// Explicit loading
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 desse curso Enrollments
:
db.Entry(selectedCourse).Collection(x => x.Enrollments).Load();
Em seguida, ele carrega explicitamente a entidade relacionada de Student
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, você usa o Reference
método.
Execute a página Índice do instrutor agora e você não verá nenhuma diferença no que é exibido na página, embora tenha alterado a forma como os dados são recuperados.
Obter o código
Recursos adicionais
Links para outros recursos do Entity Framework podem ser encontrados no ASP.NET Acesso a Dados – Recursos Recomendados.
Próximas etapas
Neste tutorial, você:
- Aprendeu a carregar dados relacionados
- Criou uma página Cursos
- Criou uma página Instrutores
Vá para o próximo artigo para aprender a atualizar dados relacionados.