ASP .NET MVC 3 - Lendo dados relacionados com EF - V
ASP .NET MVC - Lendo dados relacionados com EF - V
Esta é quinta parte do artigo e se você esta chegando agora deve ler obrigatoriamente as partes anteriores:
ASP .NET MVC 3 - Alterando o modelo de dados - Data Annotations - IV
Neste artigo vamos mostrar como ler e exibir dados relacionados, isto é, os dados que o Entity Framework carrega através das propriedades de navegação.
A partir deste tutorial eu estou usando o Visual Studio 2012 Express For web para abrir e criar os projetos.
Carregando dados : Usando Lazy, Eager e Explicit loading
Existem várias maneiras do Entity Framework carregar dados relacionados nas propriedades de navegação de uma entidade. Vejamos as principais:
Lazy loading - Quando a entidade é lida na primeira vez, os dados relacionados não são recuperados. No entanto, na primeira vez que você tentar acessar uma propriedade de navegação, os dados requeridos para aquela propriedade de navegação são automaticamente recuperados. Isto resulta em várias consultas enviadas ao banco de dados, uma para a própria entidade e uma a cada vez que os dados relacionados na entidade tem que ser recuperados.
departamentos = context.Departamentos
For Each d As Departamento In departamentos
For Each c As Curso In d.Cursos
listaCurso.Add(d.Nome + c.Titulo)
Next For Each d As Departamento In departamentos => Consulta todos os registros de departamentos
For Each c As Curso In d.Cursos => Consulta todos os registros relacionados com departamentos
Eager Loading - Quando a entidade é lida, os dados relacionados são retornados com ela. Isto resulta em uma única consulta join que retorna todos os dados necessários. Isto é feito usando o método include.
departamentos = context.Departamentos.Include(Function(x) x.Cursos)
Next For Each d As Departamento In departamentos => Consulta todos os registros de departamentos e os registros relacionados com departamentos
Explicit Loading - É semelhante ao lazy loading, 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 objetos para uma entidade e chama o método Collection.Load para coleções ou o método Reference.Load para propriedades que tratam uma única entidade. (No exemplo a seguir, se você quiser carregar a propriedade de navegação Administrador, você dever substituir Collection(x => x.Cursos) por Reference(x => x.Administrador) )
departamentos = context.Departamentos.ToList()
context.Entry(d).Collections(Function(x) x.Cursos).Load()
Next For Each d As Departamento In departamentos => Consulta todos
os registros de departamentos
context.Entry(d).Collections(Function(x) x.Cursos).Load() =>
Consulta os registros relacionados com departamentos.
O lazy loading e o explicit loading também são conhecidos como deferred loading pois eles não retornam imediatamente os valores das propriedades,
Em geral, se você sabe que precisa dos dados relacionados para todas as entidades recuperadas, o eager loading oferece um melhor desempenho, pois uma única consulta enviada ao banco de dados é geralmente mais eficiente do que consultas separadas para cada entidade recuperadas. Por exemplo, nos exemplos acima, suponhamos que cada departamento tem dez campos relacionados. O exemplo eager loading resultaria em apenas uma única query(join). Os exemplos usando lazy loading e explícit loading resultaria em 11 consultas.
Por outro lado, se você precisa acessar propriedades de navegação de uma entidade só raramente ou apenas para uma pequena parte de um conjunto de entidades que você está processamento, o lazy loading pode ser mais eficiente, pois o eager loading iria recuperar mais dados do que você precisa. Normalmente, você usaria o explicit loading somente quando você desabilitou o lazy loading. Um cenário em que você pode desabilitar o lazy loading é durante a serialização, quando você sabe que não precisa de todas as propriedades de navegação carregadas. Se o lazy loading estiver habilitado, todas as propriedades de navegação serão carregadas automaticamente, pois a serialização acessa todas as propriedades.
Por padrão a classe de context do banco de dados executa o lazy loading. Podemos desligar o lazy loading das seguintes formas:
- Para propriedades de navegação específicas, omita a palavra-chave virtual quando você declarar a propriedade;
- Para todas as propriedades de navegação, defina o a propriedade LazyLoadingEnabled para false;
O Lazy loading pode mascarar o código que causa problemas de desempenho. Por exemplo, um código que não especifica o eager ou o explicit loading mas processa um volume elevado de entidades e usa várias propriedades de navegação em cada iteração pode ser muito ineficiente (por causa de muitas idas e vindas ao banco de dados), mas vai trabalhar sem erros se ele depender do lazy loading. Desativar temporariamente o lazy loading é uma forma de descobrir onde o código está contando com o lazy loading, porque sem ela as propriedades de navegação serão nulas e o código irá falhar.
Criando a página Index de Cursos que exibe o nome do Departamento
Vamos continuar a nossa aplicação criando a view Index para os cursos e exibindo o nome do departamento relacionado.
A entidade Curso inclui uma propriedade de navegação que contém a entidade Departamento do departamento ao qual o curso é atribuído. Para exibir o nome do departamento em uma lista de cursos, você precisa obter a propriedade Nome da entidade Departamento que está na propriedade de navegação Curso.Departmento.
Vamos criar um controlador para a entidade Curso usando as mesmas opções do controlador Estudante. Clique com o botão direito sobre a pasta Controllers e selecione Add->Controller e definindo as opções conforme mostra a figura abaixo:
Abra o arquivo Controllers/CursoController.cs e observe o método Index abaixo:
' GET: /Curso/
Dim cursos = db.Cursos.Include(Function(c) c.Departamento)
Return View(cursos.ToList())
Note que o scaffolding especificou automaticamente o eager loading para a propriedade de navegação Departamento(db.Cursos.Include(Function(c) c.Departamento))
Vamos alterar a view Index.vbhtml gerada na pasta Views/Curso conforme mostra o código a seguir:
@ModelType IEnumerable(Of UniversidadeMacoratti.UniversidadeMacoratti.Models.Curso)
ViewData("Title") = "Cursos"
<th>Numero </th>
@Html.DisplayFor(Function(modelItem) currentItem.CursoID)
@Html.DisplayFor(Function(modelItem) currentItem.Creditos)
@Html.DisplayFor(Function(modelItem) currentItem.Departamento.Nome)
@Html.ActionLink("Edit", "Edit", New With {.id = currentItem.CursoID}) |
@Html.ActionLink("Details", "Details", New With {.id = currentItem.CursoID}) |
@Html.ActionLink("Delete", "Delete", New With {.id = currentItem.CursoID})
Fizemos as seguintes alterações no código gerado:
Alteramos o cabeçalho de Index para Cursos;
Adicionamos uma coluna sob o cabeçalho Numero que mostrar a propriedade CursoID.(Chaves primárias não são geradas). Contudo, neste caso, a chave primária é significativa e queremos exibir o seu valor.
Mudamos o titulo da última coluna de DepartmentoID (o nome da chave estrangeira para a entidade Departmento) para Departamento,
Observe que para a última coluna, o código gerado exibe a propriedade Nome da entidade Departamento que está sendo carregada na propriedade de navegação Departamento:
Executando o projeto e clicando na aba Cursos teremos o seguinte resultado:
Criando a página Index dos instrutores que exibe os cursos e as matriculas
Vamos criar agora um controlador e uma view para a entidade Instrutor a fim de exibir a view Index para os instrutores.
Esta página lê e exibe os dados relacionados da seguintes maneiras:
A lista de instrutores apresenta dados relacionados da entidade Departamento.
As entidades Instrutor e Departamento possuem um relacionamento um-para-zero-ou-um. Vamos usar o eager loading para as entidades Departamento visto que ele é mais eficiente quando você precisa dos dados relacionados para todas as linhas recuperadas da tabela primária. Neste caso, vamos exibir as atribuições de departamento para todos os instrutores exibidos.
Quando o usuário seleciona um instrutor, as entidades curso relacionados são exibidas. O instrutor do curso e as entidades estão em um relacionamento muitos-para-muitos. Vamos usar o eager loading para as entidades curso e suas entidades Departamento relacionadas. Neste caso, o lazy loading pode ser mais eficiente porque você precisa dos cursos só para o professor selecionado.
Quando o usuário seleciona um curso, os dados relacionados a partir do conjunto das entidades Matricula são exibidos. As entidades Curso e Matricula estão em um relacionamento um-para-muitos. Vamos adicionar o explicit loading para entidades matriculas e suas entidades estudantes relacionadas. (O Carregamento explícito não é necessário porque o lazy loading esta habilitado, mas isso mostra como fazer o carregamento explícito.)
Criando um View Model para a View Index dos instrutores
A página Index para os instrutores mostra 3 tabelas diferentes. Por isso vamos criar um view model que inclui 3 propriedades, cada uma tratando dos dados para uma das tabelas.
No menu project clique em New Folder e defina o nome ViewModels para a pasta.
Clique com o botão direito sobre a pasta ViewModels e selecione Add -> Class informando o nome InstrutorIndexData.vb. A seguir digite o código abaixo nesta classe:
Public Class InstrutorIndexData
Public Property Instrutores() As IEnumerable(Of Instrutor)
Public Property Cursos() As IEnumerable(Of Curso)
Public Property Matriculas() As IEnumerable(Of Matricula)
Para destacar as linhas selecionadas com uma cor de fundo vamos definir um estilo incluindo o código abaixo no arquivo Site.css na pasta Content, na seção MISC:
Vamos agora definir o controlador e as views para os instrutores.
Clique com o botão direito do mouse sobre a pasta Controllers e a seguir em Add->Controller;
Informe o nome InstrutorController e defina as demais opções conforme mostra a figura abaixo:
Abra o arquivo InstrutorController criado na pasta Controllers e verifique que foi gerado o método Index usando eager loading somente para a propriedade de navegação Departamento .
' GET: /Instrutor/
Dim instrutores = db.Instrutores.Include(Function(i) i.Diretoria)
Return View(instrutores.ToList())
Substitua o métdo Index com o código a seguir para carregar de forma condicional os dados relacionados e colocá-los na view model:
Public Function Index(id As System.Nullable(Of Int32), _cursoID As System.Nullable(Of Int32)) As ActionResult
Dim viewModel = New InstrutorIndexData()
viewModel.Instrutores = db.Instrutores.Include(Function(i) i.Diretoria).Include(Function(i) i.Cursos.[Select](Function(c) c.Departamento))
.OrderBy(Function(i) i.SobreNome)
ViewBag.InstrutorID = id.Value
viewModel.Cursos = viewModel.Instrutores.Where(Function(i) i.InstrutorID = id.Value).[Single]().Cursos
If _cursoID IsNot Nothing Then
ViewBag.CourseID = _cursoID.Value
viewModel.Matriculas = viewModel.Cursos.Where(Function(x) x.CursoID = _cursoID).[Single]().Matriculas
O método aceita uma parâmetros opcionais de consulta que fornecem o ID dos valores de um instrutor e um curso selecionado, e passa todos os dados requeridos para a view. Os parâmetros da consulta são fornecidos pelos hiperlinks Select na view.
O código inicia criando uma instância do view model e colocando-o na lista de instrutores:
viewModel.Instrutores = db.Instrutores.Include(Function(i) i.DataAdmissao).Include(Function(i) i.Cursos.[Select](Function(c) c.Departamento)).OrderBy(Function(i) i.SobreNome)
Este código especifica um eager loading para as propriedades de navegação Instrutor.Departamento e Instrutor.Cursos. Para as entidades Cursos relacionadas, eager loading é definida para propriedade de navegação Curso.Departamento usando o método Select no método Include.
Se um instrutor for selecionado , ele será retornado a partir da lista de instrutores na view model. A propriedade Cursos da view model é então carregada com as entidades Cursos a partir da propriedade de navegação Cursos dos instrutores.
viewModel.Cursos = viewModel.Instrutores.Where(Function(i) i.InstrutorID = id.Value).Single().Cursos
O método Where devolve uma coleção, mas neste caso os critérios passados para o método resultam em apenas uma única entidade instrutor devolvida. O único método converte a coleção em uma única entidade Instrutor, o que lhe dá acesso à propriedade Cursos da entidade.
Usamos o método Single em uma coleção quando você sabe que a coleção terá apenas um item. O método Single lança uma exceção se a coleção passada para ele estiver vazia ou se há mais de um item. Uma alternativa é usar SingleOrDefault, que retorna nulo se a coleção está vazia. No entanto, neste caso, ainda resultaria em uma exceção (a tentativa de encontrar uma propriedade Cursos em uma referência null), e a mensagem de exceção poderia indicar claramente a causa do problema. Quando você chama o método Single, você também pode passar na condição WHERE em vez de chamar o Where separadamente:
Em seguida, se o curso foi selecionado, o curso selecionado é recuperado a partir da lista de cursos no view model. Então propriedade Matriculas do view model é carregada com as entidades de matriculas da propriedade de navegação Matriculas desse curso.
O código da view Index.vbhtml para exibir os instrutores é dado a seguir:
@ModelType UniversidadeMacoratti.InstrutorIndexData
ViewData("Title") = "Instrutores"
<h2>Instrutores</h2>
<th>DataAdmissao</th>
<th>Diretoria</th>
@For Each item In Model.Instrutores
Dim selectedRow As String = ""
If item.InstrutorID = ViewBag.InstrutorID Then
selectedRow = "selectedrow"
@<tr class="@selectedRow" valign="top">
@Html.DisplayFor(Function(modelItem) currentItem.PrimeiroNome)
@Html.DisplayFor(Function(modelItem) currentItem.DataAdmissao)
If Not IsNothing(currentItem.Diretoria) Then
@Html.DisplayFor(Function(modelItem) currentItem.Diretoria.Localizacao)
@Html.ActionLink("Select", "Index", New With {.id = currentItem.InstrutorID})
@Html.ActionLink("Edit", "Edit", New With {.id = currentItem.InstrutorID}) |
@Html.ActionLink("Details", "Details", New With {.id = currentItem.InstrutorID}) |
@Html.ActionLink("Delete", "Delete", New With {.id = currentItem.InstrutorID})
Executando o projeto e clicando na guia Instrutores iremos obter o seguinte resultado:
Vamos agora incluir o código abaixo na view Index.vbhtml , logo a após a tag </table>, para exibir a lista de cursos relacionados com o instrutor selecionado:
@If Model.Cursos IsNot Nothing Then
@<h3>Cursos Ministrados pelo Instrutor</h3>
@For Each item As UniversidadeMacoratti.UniversidadeMacoratti.Models.Curso In Model.Cursos
If item.CursoID = ViewBag.CursoID Then
@Html.ActionLink("Select", "Index", New With {._cursoID = item.CursoID})
@item.CursoID
@item.Titulo
@item.Departamento.Nome
Executando novamente o projeto e selecionando um Instrutor teremos a exibição dos cursos ministrados:
E agora vamos exibir os estudantes matriculados em um curso selecionado. Para isso inclua o código abaixo no arquivo index.vbhtml:
@If Model.Matriculas IsNot Nothing Then
@<h3>Estudantes matriculados no Curso</h3>
@For Each item As UniversidadeMacoratti.UniversidadeMacoratti.Models.Matricula In Model.Matriculas
@item.Estudante.Nome
@Html.DisplayFor(Function(modelItem) currentItem.Grau)
Agora executando o projeto novamente e selecionando um instrutor veremos os estudantes matriculados no curso correspondente:
Usamos o eager loading para recuperar uma lista de instrutores para a propriedade de navegação Cursos e para a propriedade Departamento de cada curso. A seguir colocamos a coleção Cursos no view model, e agora estamos acessando a propriedade de navegação matriculas a partir de uma entidade nessa coleção. Como não especificamos o eager loading para a propriedade de navegação Curso.Matriculas, os dados desta propriedade estão aparecendo na página, como resultado do lazy loading.
Se desativarmos o lazy loading sem alterar o código de qualquer outra forma, a propriedade Matriculas seria nula independentemente do número de matrículas do curso. Nesse caso, para carregar a propriedade matrículas, teríamos que especificar o eager loading ou o explicit loading. Vamos alterar o código para usar o explicit loading, substituindo o código que exibe os cursos e alunos conforme abaixo no controlador InstrutorController da pasta Controllers:
ViewBag.CursoID = _cursoID.Value
Dim selectedCourse = viewModel.Cursos.Where(Function(x) x.CursoID = _cursoID).Single()
db.Entry(selectedCourse).Collection(Function(x) x.Matriculas).Load()
For Each enrollment As Matricula In selectedCourse.Matriculas
db.Entry(enrollment).Reference(Function(x) x.Estudante).Load()
viewModel.Matriculas = selectedCourse.Matriculas
Note que depois de selecionar a entidade Curso , o código carrega explicitamente a propriedade de navegação Matricula do curso.
Observe que usamos o método Collection para carregar a propriedade de coleção, mas para uma propriedade que possui apenas uma entidade, usaríamos o método de referência. Podemos executar a página Index Instrutor agora e não vai haver nenhuma diferença no que é exibido na página.
Assim vimos como acessar e exibir dados usando lazy, eager e explicit loading com Entity Framework.
Acompanhe a continuação aqui : ASP .NET MVC - Atualizando dados relacionados com Entity Framework - VI
Pegue o projeto completo aqui: UniversidadeMacoratti_5.zip (abra no Visual Studio 2012 Express for web)