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;
ColumnAttribute Usada 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:
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: