Entity Frameweork 4 - Usando Data Annotations


 Esta prevista para o primeiro semestre deste ano uma nova versão do Entity Framework 4 com muitos ajustes com o objetivo de tornar a ferramenta cada vez melhor.

Enquanto a versão final não sai, vamos falar um pouco sobre uma das suas últimas atualizações, a Entity Framework Feature Community Technology Preview - CTP5, mais precisamente da funcionalidade Code-First.

Obs: Baixe a versão CTP5 aqui : http://tinyurl.com/5rg52tg

Nota: A versão anterior, a CTP4, já permitia usar o recurso do First-Code com um banco de dados existente mas exigia mais intervenção do desenvolvedor com a CTP5 tudo ficou mais simples.

O Entity Framework é uma ferramenta OR/M que realiza o mapeamento objeto relacional gerando entidades e mapeando-as para as tabelas do banco de dados.

Na primeira versão do Entity Framework praticamente não havia o que é conhecido como Code-First (Código Primeiro), ou seja, a possibilidade de gerar o modelo de negócios e suas entidades sem ter que primeiro criar o banco de dados.

Para quem trabalha usando o paradigma da orientação a objetos deveria ser natural inicar o desenvolvimento pelo modelo de entidades, onde as classes são definidas para representar o domínio do negócio. Posteriormente, e a partir destas classes, seria gerado o banco de dados usado pela aplicação para realizar a persistência das informações.

Deveria ser assim, mas a primeira versão do EF não permitia esse recurso.

Tentando contornar este problema eram feitos certos malabarismos, mas ainda assim, havia uma dependência muito forte do mapeamento gerado pelo EF4 através do Entity Data Model, o que incomodava muita gente; até que a Microsoft liberou atualizações que permitem atualmente uma maior independência do Framework através da criação de classes POCO que não herdam das classes base geradas pelo EF.

Quando decidimos usar o Code-First não precisamos começar nossa aplicação criando o banco de dados ou definindo um esquema mas podemos iniciar escrevendo classes .NET para definir o modelo de objetos do nosso domínio sem ter que misturar a lógica de persistência de dados com as classes.

Neste artigo eu vou mostrar como usar Data Annotations com POCO no Code-First da CTP5.

Data Annotations

Os atributos Data Annotation foram introduzido no .NET 3.5 como uma forma de adicionar a validação para as classes usadas por aplicações ASP.NET. Desde aquela época, o RIA Services começou a usar anotações de dados e eles agora fazem parte do Silverlight também. No EF 4.0 o Code-First permite que você construa um EDM usando código (C#/VB .NET) e também permite realizar a validação com atributos Data Annotations.

Para este recurso devemos usar o namespace System.ComponentModel.DataAnnotations pois é ele que provê atributos de classes (usados para definir metadados) e métodos que podemos usar em nossas classes para alterar as convenções padrão e definir um comportamento personalizado que pode ser usado em vários cenários.

A seguir temos a relação das principais Annotations suportadas pelo Entity Framework CTP5 :

KeyAttribute - Usada para especificar que uma propriedade/coluna é parte da chave primária da entidade e se aplica apenas a propriedades escalares;
StringLengthAttribute - Usada para especificar o tamanho máximo de uma string;
ConcurrencyCheckAttribute - Usada para especificar que uma propriedade/coluna tem um modo de concorrência "fixed " no modelo EDM;
RequiredAttribute : - Usada para especificar que uma propriedade/coluna é não-nula e aplica-se a propriedades escalares, complexas, e de navegação;
ColumnAttributeUsada para especificar o nome da coluna, a posição e o tipo de dados ;
TableAttribute Usada para especificar o nome da tabela e o esquema onde os objetos da classe serão atualizados;
ForeignKeyAttribute - Usado em uma propriedade de navegação para especificar a propriedade que representa a chave estrangeira da relação
DatabaseGeneratedAttribute -Usada em uma propriedade para especificar como o banco de dados gera um valor para a propriedade, ou seja, Identity, Computed ou None;
NotMappedAttribute – Usada para definir que a propriedade ou classe não estará no banco de dados;

Usando Data Annotations

Como exemplo de utilização eu vou mostrar como podemos usar o Data Annotations para definir os requisitos das propriedades e validação das entidades com POCO usando o Entity Framework 4 CTP com Code-First.

Para não perdermos tempo, visto que o foco do artigo é mostrar o uso das Annotations, eu vou usar o mesmo modelo do projeto EF4_Poco_Escola que foi criado no artigo - Entity Framework 4 - Usando POCO, Code First e as convenções padrão;

Neste artigo eu mostrei como usar o Code-First para criar o banco de dados e as tabelas. A seguir temos o modelo que usamos no projeto:

O nosso modelo de negócio é composto por duas classes:
  • Curso
  • Aluno

O modelo de classes representa um Curso que possui 1 ou mais alunos.

Temos aqui uma associação que representa o relacionamento um-para-muitos.

O relacionamento um-para-muitos é usado quando uma entidade A pode se relacionar com uma ou mais entidades B.  Este relacionamento é representado pelo sinal: 1:N ou 1: * que expressa a cardinalidade do relacionamento.

A cardinalidade é um conceito importante para ajudar a definir o relacionamento, ela define o número de ocorrências em um relacionamento. Para determinar a cardinalidade, deve-se fazer a pergunta relativa ao relacionamento em ambas as direções. No exemplo a seguir, temos

Obs: Você vai precisar ter instalado o SQL Server 2005 ou 2008 Express Edition.

Vou usar o mesmo modelo acima e mostrar como usar o Data Annotations para definir alguns requisitos que desejamos em nosso novo projeto.

Vamos definir então os seguintes requisitos:

E a seguir mostrar como podemos implementá-los usando Data Annotations no CTP5 do Entity Framework.

Criando o projeto no Visual Basic 2010 Express Edition

Abra o VB 2010 Express Edition e no menu File clique New Project e a seguir selecione o template Windows Forms Application informando o nome EF4_Poco_DataAnnotations e clicando no botão OK;

Definindo as classes de negócio com Data Annotations

Vamos definir as classes Curso e Aluno no projeto da seguinte forma:

1 - No menu Project selecione Add Class e informe o nome Modelo.vb e clique no botão Add;

A próxima tarefa será definir as classes Curso e Aluno no arquivo Modelo.vb;

A primeira coisa a fazer é declarar o namespace Imports System.ComponentModel.DataAnnotations no arquivo Modelo.vb;

Obs: Se você encontrar problemas, como o não reconhecimento dos atributos, referencia o namespace no seu projeto.

Após isso vamos iniciar com a classe Curso implementando os requisitos definidos:

1- Abaixo vemos o código da classe:

Imports System.ComponentModel.DataAnnotations

<Table("CursosMacoratti")> _
Public Class Curso

   
<Key()> _
    <DatabaseGenerated(DatabaseGenerationOption.Identity)> _

    Public Property CodigoCurso() As Integer
        Get
            Return m_CursoId
        End Get
        Set(ByVal value As Integer)
            m_CursoId = value
        End Set
    End Property
    Private m_CursoId As Integer

    
<StringLength(50)> _
    <Required(ErrorMessage := "O nome do curso é obrigatório!")> _

    Public Property Nome() As String
        Get
            Return m_nome
        End Get
        Set(ByVal value As String)
            m_nome = value
        End Set
    End Property
    Private m_nome As String

    Private m_escolaid As Integer
    Public Property EscolaId() As Integer
        Get
            Return m_escolaid
        End Get
        Set(ByVal value As Integer)
            m_escolaid = value
        End Set
    End Property

    Public Property Alunos() As ICollection(Of Aluno)
        Get
            Return m_Alunos
        End Get
        Set(ByVal value As ICollection(Of Aluno))
            m_Alunos = value
        End Set
    End Property
    Private m_Alunos As ICollection(Of Aluno)
End Class

- A <Table("CursosMacoratti")> define o nome da tabela que será gerada;

- A Annotation <Key()> informa que o atributo será a chave primária da tabela;

- A Annotation <DatabaseGenerated(DatabaseGenerationOption.Identity)> garante que o campo será do tipo identity;

- A Annotation <StringLength(50)> define que campo string terá um tamanho de 50 caracteres;

- A Annotation <Required(ErrorMessage := "O nome do curso é obrigatório!")> define que o campo Nome é obrigatório;

Vamos agora definir classe Aluno implementando os requisitos definidos usando praticamente os mesmos atributos;

2- Abaixo vemos o código da classe Aluno;

<Table("AlunosMacoratti")> _
Public Class Aluno

    Private m_AlunoId As Integer
 
   <Key()> _
    <DatabaseGenerated(DatabaseGenerationOption.Identity)>
_

    Public Property CodigoAluno() As Integer
        Get
            Return m_AlunoId
        End Get
        Set(ByVal value As Integer)
            m_AlunoId = value
        End Set
    End Property

    Private m_nome As String
  
 <StringLength(50)> _
   <Required(ErrorMessage:="O nome do Aluno é obrigatório!")> _

    Public Property Nome() As String
        Get
            Return m_nome
        End Get
        Set(ByVal value As String)
            m_nome = value
        End Set
    End Property

    Private m_Email As String
 
  <StringLength(100)> _
   <Required(ErrorMessage:="O Email do aluno é obrigatório!")> _

    Public Property Email() As String
        Get
            Return m_Email
        End Get
        Set(ByVal value As String)
            m_Email = value
        End Set
    End Property

    Public Property Curso() As Curso
        Get
            Return m_curso
        End Get
        Set(ByVal value As Curso)
            m_curso = value
        End Set
    End Property
    Private m_curso As Curso

End Class

A seguir vamos definir a classe para criar e acessar o banco de dados.

3 - No menu Project selecione Add Class e informe o nome AcessoDados.vb e clique no botão Add;

Vamos definir a classe AcessoDados que herda de DbContext:

O construtor Sub New() define o banco de dados que será criado e os métodos definem os nomes das tabelas geradas;

Definindo a interface

No formulário form1.vb vamos definir uma interface bem simples apenas para exibir as mensagens durante a execução do código.

Inclua um controle ListBox (lstResultado) e um botão de comando (btnExecutar) conforme o leiaute da figura abaixo:

O código do formulário pode ser visto abaixo:

- No evento Click do botão de comando temos a chamada à rotina geraTabelasBD() cujo código vemos também a seguir:

Imports EF4_Poco_DataAnnotations.AcessoDados

Public Class Form1

    Private Sub btnExecutar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExecutar.Click
        geraTabelasBD()
    End Sub

    Private Sub geraTabelasBD()
        lstResultado.Items.Clear()
        lstResultado.Items.Add("Gerando as tabelas e o banco de dados")
        Using contextoCurso As New ContextoCurso()

            Dim curso As New Curso() With {
              .Nome = "Entity Framework",
              .EscolaId = 100
            }

            lstResultado.Items.Add("")
            lstResultado.Items.Add("Entidade Curso criada com sucesso: nome='Entity Framework'")

            contextoCurso.CursosMacoratti.Add(curso)

            lstResultado.Items.Add("")
            lstResultado.Items.Add("O curso foi incluído na coleção Cursos")

            Dim macoratti As New Aluno() With
            {
                 .Nome = "Jose Carlos Macoratti",
                 .Curso = curso,
                 .Email = "macoratti@yahoo.com"
            }

            lstResultado.Items.Add("")
            lstResultado.Items.Add("Entidade Aluno criada com sucesso: nome='Jose Carlos Macoratti'")

            contextoCurso.AlunosMacoratti.Add(macoratti)
            lstResultado.Items.Add("O aluno foi incluído na coleção Alunos")
            Dim registros As Integer = contextoCurso.SaveChanges()
            lstResultado.Items.Add("As entidades foram persistidas")
            lstResultado.Items.Add("")
            lstResultado.Items.Add("Registros afetados :" + registros.ToString())

        End Using
    End Sub
End Class

Neste código temos que:

– O EF tenta vai tentar se conectar em uma instância padrão do SQL Express (./SQLEXPRESS) criando o banco de dados EscolaMacoratti se ele ainda não existir;
– O nome do banco de dados será o definido na classe de contexto com o construtor 'base' ou então será o nome da classe.(conforme já mostrei no artigo :
Entity Framework 4 - Usando POCO, Code First e as convenções padrão)
- A implementação dos requisitos que definimos via Data Annotations será criada;

Executando o projeto temos , após alguns instantes, o resultado exibido no formulário do projeto:

Feito isso , vamos verificar como o Entity Framework se comportou e quais as operações que ele realizou...

Vamos dar uma olhada no SQL Server instalado na máquina local através do SQL Server Management Studio...

Na figura abaixo vemos que o banco de dados EscolaMacoratti foi criado como definido.

Foram criadas também as tabelas AlunosMacoratti e CursosMacoratti e também foram persistidas os dados conforme mostra a figura a seguir:

- Foi gerada a tabela AlunosMacoratti

- A chave primária da tabela é CodigoAluno

- A chave primária é do tipo identity

- Foi gerada a tabela CursosMacoratti

- A chave primária da tabela é CodigoCurso

- A chave primária é do tipo identity

Examinando as respectivas tabelas vemos também que as informações foram persistidas no banco de dados:

Tabela AlunosMacoratti
Tabela CursosMacoratti

Verificando a validação

Falta verificamos se os requisitos definidos para os campos e para a validação , onde definimos que o nome do curso, o nome e email do aluno eram obrigatórios estão funcionando.

Vamos incluir um novo botão de comando - Testando a validação - no formulário com o nome btnTesteValidacao;

A seguir vamos incluir no evento Click deste botão praticamente o mesmo código que usamos para testar a criação do banco de dados, tabelas e persistência só que não vamos informar o nome do curso nem o nome e email do aluno. O código é visto abaixo:

  Private Sub btnTesteValidacao_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
 btnTesteValidacao.Click

        Try
            Using contextoCurso As New ContextoCurso()

                Dim curso As New Curso() With {
                  .Nome = "",  'nome do curso vazio
                  .EscolaId = 100
                }

                contextoCurso.CursosMacoratti.Add(curso)

                Dim macoratti As New Aluno() With
                {
                     .Nome = "", 'nome do aluno vazio
                     .Curso = curso,
                     .Email = ""  'email do aluno vazio
                }

                lstResultado.Items.Add("")
                contextoCurso.AlunosMacoratti.Add(macoratti)
                Dim registros As Integer = contextoCurso.SaveChanges()
                lstResultado.Items.Add("")

                lstResultado.Items.Add("Registros afetados :" + registros.ToString())
            End Using

        Catch ex As DbEntityValidationException
            For Each falha In ex.EntityValidationErrors
                lstResultado.Items.Add("Falha na validação : " & falha.Entry.Entity.[GetType]().ToString)
                For Each erro In falha.ValidationErrors
                    lstResultado.Items.Add(erro.PropertyName + " " + erro.ErrorMessage)
                    lstResultado.Items.Add("")
                Next
            Next
        End Try

    End Sub

Para verificarmos se a validação foi efetuado vamos verificar os erros no objeto usando um bloco Try/Catch e verificando o objeto DbEntityValidationException;

O EF Code-First automaticamente aplica as regras Data Annotation quando o modelo de objetos for atualizado ou persistido. Não temos que escrever qualquer código para realizar esta aplicação pois ela é suportada por padrão.

O código que violar as regras definidas via Data Annotation irá lançar uma exceção na execução do método SaveChanges().

A exceção DbEntityValidationException  que é lançada contém a propriedade EntityValidationErrors de onde podemos retornar a lista de todos os erros de validação ocorridos.

obs: A exceção DbEntityValidationException é lançada a partir da execução do método SaveChages() se a validação falhar.

Percorrendo o EntityValidationErrors exibimos o nome da Entidade onde a falha ocorreu e em ValidationErrors exibimos o nome e a mensagem de erro conforme definimos usando Data Annotations.

Conclusão : Todos os requisitos definidos foram implementados usando Data Annotations de uma maneira bem simples: Nosso banco de dados foi gerado, as tabelas também, as chaves primárias do tipo identity e a validação foi realizada conforme as regras que definimos.

Parabéns ao Entity Framework e ao Data Annotations (Estou quase usando o danado em produção...)

Pegue o projeto completo aqui: EF4_Poco_DataAnnotations.zip

Eu sei é apenas , mas eu gosto...

"Falou-lhes pois Jesus outra vez, dizendo: Eu sou a luz do mundo; quem me segue não andará em trevas, mas terá a luz da vida." (João 8:12)

Referências:

José Carlos Macoratti