ASP .NET MVC - Lendo dados relacionados com EF - V


Este artigo é baseado no original: Creating an Entity Framework Data Model for an ASP.NET MVC Application (1 of 10) com adaptações e pequenos ajustes feitos por mim.

Antes de prosseguir verifique se você possui os seguintes recursos instalados:

Esta é quinta parte do artigo e se você esta chegando agora deve ler obrigatoriamente as partes anteriores:

  1. ASP .NET MVC 3 - Criando uma aplicação ASP .NET MVC - I
  2. ASP .NET MVC 3 - Implementando as funcionalidades CRUD básicas com EF - II
  3. ASP .NET MVC 3 - Ordenação, Filtragem e Paginação com EF - III
  4. 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
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)

For Each d As Departamento In departamentos
For Each c As Curso In d.Cursos
listaCurso.Add(d.Nome + c.Titulo)
Next
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()

For Each d As Departamento In departamentos
          context.Entry(d).Collections(Function(x) x.Cursos).Load()
          For Each c As Curso In d.Cursos
listaCurso.Add(d.Nome + c.Titulo)
Next
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:

  1. - Para propriedades de navegação específicas, omita a palavra-chave virtual quando você declarar a propriedade;
  2. - 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/
 Function Index() As ViewResult
      Dim cursos = db.Cursos.Include(Function(c) c.Departamento)
      Return View(cursos.ToList())
 End Function

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)

@Code
    ViewData("Title") = "Cursos"
End Code
<h2>Cursos</h2>
<p>
    @Html.ActionLink("Criar Novo", "Create")
</p>
<table>
    <tr>
       <th>Numero </th>
       <th>Titulo</th>
       <th>Creditos</th>
       <th>Departamento</th>
        <th></th>
    </tr>
@For Each item In Model
    Dim currentItem = item
    @<tr>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.CursoID)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Titulo)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Creditos)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Departamento.Nome)
        </td>
        <td>
            @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})
        </td>
    </tr>
Next
</table>

Fizemos as seguintes alterações no código gerado:

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:

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:

Imports UniversidadeMacoratti.UniversidadeMacoratti.Models
Imports System.Collections.Generic

Public Class InstrutorIndexData

    Public Property Instrutores() As IEnumerable(Of Instrutor)
    Public Property Cursos() As IEnumerable(Of Curso)
    Public Property Matriculas() As IEnumerable(Of Matricula)

End Class

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:

/* MISC 
----------------------------------------------------------*/
.selectedrow
{
    background-color: #EEEEEE;
}

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/
        Function Index() As ViewResult
            Dim instrutores = db.Instrutores.Include(Function(i) i.Diretoria)
            Return View(instrutores.ToList())
        End Function

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)

            If id IsNot Nothing Then
                ViewBag.InstrutorID = id.Value
                viewModel.Cursos = viewModel.Instrutores.Where(Function(i) i.InstrutorID = id.Value).[Single]().Cursos
            End If

            If _cursoID IsNot Nothing Then
                ViewBag.CourseID = _cursoID.Value
                viewModel.Matriculas = viewModel.Cursos.Where(Function(x) x.CursoID = _cursoID).[Single]().Matriculas
            End If

            Return View(viewModel)
End Function

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:

Dim viewModel = New InstrutorIndexData()
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.

If id IsNot Nothing Then
    ViewBag.InstrutorID = id.Value
    viewModel.Cursos = viewModel.Instrutores.Where(Function(i) i.InstrutorID = id.Value).Single().Cursos
End If

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

@Code
    ViewData("Title") = "Instrutores"
End Code

<h2>Instrutores</h2>

<p>
    @Html.ActionLink("Criar Novo", "Create")
</p>
<table>
    <tr>
        <th>SobreNome</th>
        <th>Nome</th>
        <th>DataAdmissao</th>
        <th>Diretoria</th>
        <th></th>
    </tr>

@For Each item In Model.Instrutores
    Dim currentItem = item
    Dim selectedRow As String = ""
    If item.InstrutorID = ViewBag.InstrutorID Then
        selectedRow = "selectedrow"
    End If
    @<tr class="@selectedRow" valign="top">
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.SobreNome)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.PrimeiroNome)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.DataAdmissao)
        </td>
        <td>
            @Code
                If Not IsNothing(currentItem.Diretoria) Then
                   @Html.DisplayFor(Function(modelItem) currentItem.Diretoria.Localizacao)                  
                End If
            End Code
        </td>
        <td>
            @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})
        </td>
    </tr>
Next
</table>

Executando o projeto e clicando na guia Instrutores iremos obter o seguinte resultado:

Com isso terminamos essa parte aguarde em breve a abordagem de outros tópicos relacionados ao assunto.

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> 
    @<table> 
        <tr> 
            <th></th> 
            <th>ID</th> 
            <th>Titulo</th> 
            <th>Departamento</th> 
        </tr> 
 
    @For Each item As UniversidadeMacoratti.UniversidadeMacoratti.Models.Curso In Model.Cursos
            Dim selectedRow As String = ""
            If item.CursoID = ViewBag.CursoID Then
                selectedRow = "selectedrow"
            End If
    @<tr class="@selectedRow"> 
        <td> 
            @Html.ActionLink("Select", "Index", New With {._cursoID = item.CursoID}) 
        </td> 
        <td> 
            @item.CursoID 
        </td> 
        <td> 
            @item.Titulo 
        </td> 
        <td> 
            @item.Departamento.Nome 
        </td> 
    </tr> 
    Next
 
</table> 
End If

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> 
    @<table> 
        <tr> 
            <th>Nome</th> 
            <th>Grau</th> 
        </tr> 
     @For Each item As UniversidadeMacoratti.UniversidadeMacoratti.Models.Matricula In Model.Matriculas
            Dim currentItem = item
    @<tr> 
        <td> 
            @item.Estudante.Nome
        </td> 
        <td> 
            @Html.DisplayFor(Function(modelItem) currentItem.Grau)
        </td> 
    </tr> 
    Next
 </table> 
End If

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:

   If _cursoID IsNot Nothing Then
           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()
           Next

          viewModel.Matriculas = selectedCourse.Matriculas

    End If

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.

Pegue o projeto completo aqui:   UniversidadeMacoratti_5.zip (abra no Visual Studio 2012 Express for web)

"E a ninguém na terra chameis vosso pai, porque um só é o vosso Pai, o qual esta nos céus." "Nem vos chameis mestres, porque um só é o vosso Mestre, que é o Cristo." Mateus 23:9-10

Referências:

  1. VB .NET - Implementando o relacionamento um-para-muitos
  2. Entity Framework 4.0 - Lazy Loading - Macoratti.net
  3. Entity Framework 4.0 - Carregando dados - Macoratti.net
  4. Entity Framework 4.0 - Características e operações ... - Macoratti.net
  5. Entity Framework - Mestre Detalhes - Macoratti.net
  6. VS 2010 Novidades
  7. C# - Os tipos Nullable (Tipos Anuláveis)
  8. VB .NET - Nothing , Null (DBNull.Value) e Nullabe Types
  9. Entity Framework 4.0 - Lazy Loading - Macoratti.net
  10. ASP .NET MVC - Introdução
  11. ASP .NET - Apresentando o ASP .NET MVC 3 - Macoratti.net

José Carlos Macoratti