Entity Framework 4.1 - Complex Type via Code-First


Um dos recursos mais interessantes da versão 4.1 do Entity Framework é o Code-First, um novo modelo de desenvolvimento, que fornece uma maneira elegante de trabalhar com dados usando classes POCO.

O Code-First é projetado com base no paradigma Convenção sobre configuração e esta centrado em torno da definição do seu modelo usando classes C# /VB.NET; onde essas classes podem ser mapeadas para um banco de dados existente ou serem usadas para gerar um esquema de banco de dados.

A Convenção sobre configuração (também conhecidas como codificação por convenção) é um paradigma de projeto de software que visa diminuir o número de decisões que os desenvolvedores precisam fazer, ganhando simplicidade, mas não necessariamente perdendo a flexibilidade.Neste paradigma o desenvolvedor precisa somente especificar aspectos não-convencionais da aplicação.Por exemplo se existe uma classe Venda no modelo por padrão a tabela correspondente no banco de dados será chamada 'Vendas'.( http://en.wikipedia.org/wiki/Convention_over_configuration)

Tenho escrito muitos artigos sobre este novo recurso e hoje vou falar sobre Complex Type com Code-First.

Antes de entrar direto no assunto vou recordar alguns conceitos importantes para que possamos compreender os Tipos Complexos.

No EF trabalhamos com entidades/objetos/classes que são mapeadas para tabelas de um banco de dados relacional. O gerenciamento das associações entre classes e os relacionamentos entre as tabelas pode ser visto como um dos aspectos mais importantes quando utilizamos o EF na prática, pois, as dificuldades de implementação de uma solução usando o EF como um ORM estão relacionadas com o gerenciamento dessas associações.

O que vem a ser um mapeamento ?
O mapeamento é o ato de determinar como os objetos e suas relações são mantidas em um armazenamento de dados persistente, no nosso caso, nos bancos de dados relacionais.

O que é mapeamento de relacionamento ?
É um mapeamento que descreve como persistir um relacionamento (associação, agregação ou composição) entre dois ou mais objetos.

Quais os tipos de relacionamentos ?

Existem duas categorias de relacionamentos de objetos com os quais devemos nos preocupar quando mapeamos associações.

1- A primeira categoria é baseada na multiplicidade e inclui três tipos:

2- A segunda categoria é baseada na direcionalidade e contém dois tipos:

Relacionamento Uni-direcional: Ocorre quando um objeto sabe sobre como o objeto(s) está(ão) relacionados, mas o outro objeto(s) não sabe do objeto original. Para colocar isto em uma terminologia do Entity Framework podemos dizer que neste caso temos que a propriedade de navegação existe apenas em uma das extremidades da associação e não em ambas;

Relacionamento Bi-Direcional - Ocorre quando os objetos em ambos os lados do relacionamentos se conhecem, ou seja, existe um propriedade de navegação definida em ambos os lados dos relacionamentos;

Como o relacionamento entre objetos é implementado usando POCO ?
Quando a multiplicidade é um (ex: 0..1 ou 1) o relacionamento é implementado através da definição de uma propriedade de navegação que faze referência a outro objeto (por exemplo, uma propriedade Endereco na classe Cliente). Quando a multiplicidade é muitos(ex: 0..*, 1..*) o relacionamento é implementado através de uma coleção (ICollection) do tipo de outro objeto.

Como o relacionamento existente nos Banco de dados Relacionais são implementados?
Os Relacionamentos em bancos de dados relacionais são mantidos através do uso de chaves estrangeiras. Uma chave estrangeira é um atributo de dados que aparece em uma tabela e deve ser a chave primária ou chave de outro candidato em outra tabela. Com um relacionamento um-para-um a chave estrangeira deve ser implementada por uma das tabelas. Para implementar um relacionamento um-para-muitos nos definimos uma chave estrangeira a partir da "tabela um" para a "tabela muitos". Poderíamos também optar por implementar um relacionamento um-para-muitos através de uma tabela associativa, efetivamente tornando-a um relacionamento muitos-para-muitos.

isto posto vamos agora ao assunto do artigo...

Definindo um Complex Type

Vamos criar um cenário onde temos que definir o modelo de entidades onde temos as entidades Cliente e Endereco e a partir do qual iremos criar um complex type com o EF Code-First.

Neste cenário a informação do endereço do cliente é modelada como uma classe separada conforme o diagrama abaixo:

Em termos de modelagem de objetos esta associação é um tipo de agregação.

Se pensarmos que um Cliente pode ter um endereço residencial e um endereço comercial vamos concluir que este modelo faz sentido em termos de objetos.

Em termos de banco de dados podemos ter uma única tabela Cliente contendo os campos:

Esta desnormalização no modelo relacional pode ser considerada coerente e correta e ela pode ser usada por vários motivos um dos quais o desempenho.

Em nosso modelo de objetos poderíamos usar a mesma abordagem mas é melhor e mais coerente definir um modelo com uma classe Endereco separada da classe Cliente onde o cliente possui as propriedades EnderecoComercial e EnderecoResidencial.

Este modelo de objetos possui mais coerência e permite uma melhor reutilização visto que podemos ter outras classes com relacionamento para a classe Endereco.

Neste cenário podemos definir o que é conhecido no entity framework como Complex Type.

O que é um Complex Type ?

Complex Types ou Tipos Complexos são propriedades não-escalares de tipos de entidades que permitem que propriedades escalares sejam organizadas no âmbito de entidades. Como as entidades eles consistem em propriedades escalares ou outros tipos complexos que não possuem chaves e não podem ser gerenciados pelo EF separados do seu objeto Pai.

Ao trabalhar com objetos que representam tipos complexos, esteja atento aos seguintes fatores:

  1. Um Tipo complexo não pode conter propriedades de navegação;
  2. As propriedades de um tipo complexo não podem ser null;
  3. Tipos complexos não podem herdar de outros tipos complexos;
  4. Quando qualquer propriedade é alterada em qualquer lugar do gráfico do objeto de um tipo complexo, a propriedade do tipo do pai é marcada como alterada e todas as propriedades do objeto gráfico do tipo complexo são atualizados quando SaveChanges for chamado;
  5. Quando a camada do objeto é gerada pelo Entity Data Model, os objetos complexos são instanciados quando a propriedade do tipo complexo é acessada, e não quando o objeto pai é instanciado;

Dessa forma um tipo complexo não possui uma identidade individual estando associado ao objeto Pai de forma que quando for persistido no banco de dados ele o será na mesma tabela do objeto Pai.

O Code-First possui o conceito de "Descoberta de Tipo Complexo" que funciona com base em um conjunto de convenções. A convenção é que se o Code-First descobre uma classe onde uma chave primária não pode ser deduzida, e, nenhuma chave primária esta registrada por meio de Data Annotations (anotações de dados) ou da Fluent API, então o tipo será automaticamente registrado como um tipo complexo.

EF Code-First - Implementando um Tipo Complexo

Vamos utilizar o Visual Basic 2010 Express Edition como ambiente para implementar um tipo complexo levando em conta o modelo definido acima.

Abra o Visual Studio e crie um novo projeto do tipo Class Library com o nome Tipo_Complexo:

A seguir altere nome do arquivo Class1.vb criado por padrão para Cliente.vb e no menu Project clique em Add Class e informe o nome Endereco.vb para incluir uma nova classe Endereco.vb no projeto.

Neste momento temos uma solução com um projeto com duas classes vazias. Conforme a figura abaixo:

Antes de continuar temos que incluir uma referência ao Entity Framework 4.1 no nosso projeto;

- Clique com o botão direito do mouse sobre o projeto Tipo_Complexo e clique em Add Reference;
- Na janela Add Reference clique na guia Browse e selecione o assembly EntityFramework.dll do local onde você instalou o Entity Framework 4.1 e clique em OK;

Vamos incluir também uma referência ao namespace System.Data.Entity repetindo o processo assim e selecionando este namespace a partir da guia .NET conforme figura abaixo:

O próximo passo é definir as classes conforme o código a seguir:

1- Classe Cliente

Public Class Cliente

    Public Property ClienteId As Integer
    Public Property Nome As String
     Public Property Endereco As Endereco
End Class

2- Classe Endereco

Public Class Endereco

    Public Property Cep As String
    Public Property Rua As String
    Public Property Cidade As String
    Public Property Estado As String

End Class

Para completar temos que definir a classe que representa o nosso contexto.

O Entity Framework 4.1 traz como novidade uma versão simples das classes : ObjectContext e ObjectSet onde:

- ObjectContext permite consultar, controle de alterações e salvar no banco de dados.
- ObjectSet encapsula os conjuntos de objetos semelhantes.

O DbContext é um invólucro para ObjectContext e além disso esta classe contém:

- Um conjunto de APIs que são mais fáceis de usar do que a exposta pelo ObjectContext;
- As APIs que permitem utilizar o recurso do Code-First e as convenções;

O DbSet é um invólucro para ObjectSet e permite que na sua execução o banco de dados e as tabelas sejam criados com base na modelo definido.

O EF Code First permite que você conecte facilmente suas classes POCO do modelo em um banco de dados através da criação de uma classe "DbContext" que expõe propriedades públicas que mapeiam para tabelas do banco de dados.

Vamos então incluir uma nova classe chamada Contexto que herda de DbContext conforme o código abaixo:

Imports System.Data.Entity

Public Class Contexto
    Inherits DbContext

    Public Clientes As DbSet(Of Cliente)

End Class

Dessa forma temos tudo pronto para gerar o nosso modelo de entidades e o respectivo banco de dados e tabelas usando o Code-First e as convenções padrão.

Vamos antes disso incluir um novo projeto na solução a partir do menu File->Add->New Project e escolher o template Windows Forms Application com o nome TIpo_Complexo_Interface;

Selecione o projeto e com o botão direito do mouse escolha o item : Set as Startup Project.

Vamos então definir uma referência ao projeto Tipo_Complexo ao projeto TIpo_Complexo_Interface selecionando este último e via menu Project clicando em Add Reference;

Na guia Projects selecione o projeto e clique em OK;

Vamos definir também a referência ao Entity Framework 4.1 neste projeto:

- Clique com o botão direito do mouse sobre o projeto Tipo_Complexo_Interface e clique em Add Reference;
- Na janela Add Reference clique na guia Browse e selecione o assembly EntityFramework.dll do local onde você instalou o Entity Framework 4.1 e clique em OK;

No formulário form1.vb o controle DataGridView (gdvDados) e um controle Button (btnExibir) conforme a figura abaixo:

No evento Click do botão de comando insira o código a seguir:

Imports Tipo_Complexo
Public Class Form1

    Private Sub btnExibir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExibir.Click

      Using ctx = New Contexto()
            Dim cli As New Cliente() With { _
              .Nome = "Macoratti" _
            }
            ctx.Clientes.Add(cli)
            ctx.SaveChanges()
        End Using

    End Sub
End Class

Executando o projeto iremos obter uma mensagem de erro conforme mostra a figura:

Mas qual o problema ?

Simples: Temos que inicializar o objeto Endereco. Isso mesmo, os tipos complexos são sempre requeridos.

Vamos então ajustar o código e inicializar o tipo complexo...

Imports Tipo_Complexo
Public Class Form1

    Private Sub btnExibir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExibir.Click

      Using ctx = New Contexto()
            Dim cli As New Cliente() With { _
              .Nome = "Macoratti", .Endereco = New Endereco()
            }
            ctx.Clientes.Add(cli)
            ctx.SaveChanges()
        End Using

    End Sub
End Class

Executando novamente o código o erro não irá ocorrer.

Após executar o código vamos então abrir o SQL Server Management Studio e espiar o que o

1- O banco de dados foi gerado com o nome padrão : Nome da Solução+Nome do Contexto = Tipo_Complexto.Contexto;
2- A tabela Clientes foi gerada com os campos das classes Cliente e Endereco onde o tipo complexo Endereco foi usado para prefixar o nome das colunas do endereço;
3- O objeto Endereço foi definido como null e o respectivo tipo complexo também é inicializado com os valores null;

Vamos então definir uma rotina para gravar dados do cliente com seu endereço:

  Private Sub definirDados()
        Using ctx = New Contexto()

            Dim cli As New Cliente() With { _
                     
.Nome = "Jefferson", _
                      .Endereco = New Endereco() With { _
                                       .Cep = 10245, _
                                       .Rua = "R. Projetada 100", _
                                       .Cidade = "Lins", _
                                       .Estado = "SP" _
                                    } _

                         }

            ctx.Clientes.Add(cli)
            ctx.SaveChanges()
        End Using
    End Sub

Após fazer isso vamos chamar esta rotina no evento Load do formulário form1.vb:

private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    definirDados()
End Sub

Finalmente vamos exibir os dados no controle DataGridView incluindo no evento Click do botão de comando o código abaixo:

 Private Sub btnExibir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExibir.Click
        Using ctx = New Contexto()
          
 Dim clientes = ctx.Clientes.Select(Function(f) New With _
                                  {f.Nome, f.Endereco.Cep, f.Endereco.Rua, f.Endereco.Cidade, f.Endereco.Estado})

            dgvDados.DataSource = clientes.ToList
        End Using
    End Sub

Observe que temos que navegar no tipo complexo para obter o valor do campo: ex: f.Endereco.Cep.

O resultado pode ser visto na figura abaixo:

Espiando o banco de dados novamente veremos:

Pegue o projeto completo aqui: Tipo_Complexo.zip

"Portanto, se já ressuscitastes com Cristo, buscai as coisas que são de cima, onde Cristo esta assentado à destra de Deus". Colossenses 3:1

Referências:


José Carlos Macoratti