Curso Entity Framework - A abordagem Code First - VI
Nesta aula vamos tratar da abordagem Code First no Entity Framework.(aula anterior) |
A partir da
versão 4.1, o Entity Framework incluiu tanto a abordagem
Code First como a nova API DbContext. Esta
API fornece uma interface mais produtiva para trabalhar com o Entity Framework e
pode ser usada com o seguintes padrões de desenvolvimento:
Code First - Criamos classes POCO que são mapeadas para as entidades;
Database First - Mapeamos para um Banco de dados que já existe;
Model First - Criamos o Modelo conceitual primeiro e depois é gerado o script para criar o banco de dados;
Na abordagem Code First você não trabalha diretamente com o designer de modelo visual (EDMX). Você escreve suas classes POCO em primeiro lugar e, em seguida, cria o banco de dados a partir dessas classes POCO (Plain Old CLR Objects). Os desenvolvedores que seguem os princípios da Domain-Driven Design (DDD) preferem começar codificando suas classes em primeiro lugar para em seguida gerar o banco de dados necessário para persistir seus dados.
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.
O Entity Framework por padrão adota algumas convenções (Conventions) que ele usa para realizar algumas operações.
Mas o que vem a ser essas convenções ???
Consultando o dicionário teremos a seguinte definição: "Acordo, pacto, contrato: convenção verbal , aquilo que está geralmente admitido ou tacitamente contratado."
Em nosso contexto, podemos dizer que uma convenção é uma regra padrão pelo qual não teremos que fazer algumas configurações de mapeamento para nossas entidades, sendo que o EF4 vai , baseado nessas convenções, realizar as tarefas de forma automática.
As convenções estão definidas por uma hierarquia de subclasses que implementam um contrato comum chamado IConvention. Uma simples definição deste contrato pode ser visto no código abaixo:
public interface IConvention { } |
Public Interface
IConvention |
C# | VB .NET |
Debaixo de IConvention temos elementos como AttributeConfigurationConvention, IDbConvention e IEdmConvention que nos interessam mais , uma vez que eles representam os contratos através dos quais foram implementadas as diversas convenções.
Podemos usar o recurso Data Annotations para definir algumas convenções com o Entity Framework no modelo Code-First.
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;
Vamos então mostrar como usar POCO sem criar o Entity Data Model e também usar o Code-First gerando o banco de dados e as tabelas a partir de nossas entidades.
Mas o que são
essas classes POCO ?
POCO - Plain
Old CLR Object - são classes simples de domínio que possuem apenas get/sets
e um construtor e que não dependem de nenhum framework; |
Vamos iniciar criando o nosso modelo de negócio através da definição das classes do nosso domínio. Para tornar a tarefa mais suave e de fácil entendimento vou adotar um modelo simplificado que pode ser visto no diagrama de classes a seguir:
O nosso modelo de
negócio consiste de 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 |
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
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 Entity Framework.
Criando o projeto no Visual Studio 2013 e usando Code-First
Abra o Visual Studio 2013 Express for Windows desktop e clique em New Project;
A seguir selecione a linguagem Visual Basic e o template Console Application e informe o nome EF_CodeFirst e clique no botão OK.
Agora vamos incluir no projeto criado uma referência ao Entity Framework.
Na janela Solution Explorer clique com o botão direito do mouse sobre o projeto EF6_Fundamentos1 e a seguir clique em Manage Nuget Packages.
A seguir clique na guia Online e na caixa de pesquisa digite EntityFramework;
Localize o pacote para o EntityFramework e clique no botão Install, confirmando e aceitando a instalação;
Podemos agora 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 os namespaces Imports System.ComponentModel.DataAnnotations e Imports System.ComponentModel.DataAnnotations.Schema no arquivo Modelo.vb;
Após isso vamos iniciar com a classe Curso implementando os requisitos definidos:
Abaixo vemos o código da classe Curso:
Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel.DataAnnotations.Schema
<Table("CursosMacoratti")> _
Public Class Curso
<Key()> _
<DatabaseGenerated(Schema.DatabaseGeneratedOption.Identity)> _
Public Property CodigoCurso() As Integer
<StringLength(50)> _
<Required(ErrorMessage:="O nome do curso é obrigatório!")> _
Public Property Nome() As String
Public Property EscolaId() As Integer
Public Property 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;
A seguir temos o código da classe Aluno onde definimos algumas convenções usando Data Annotations:
<Table("AlunosMacoratti")> _
Public Class Aluno
<Key()> _
<DatabaseGenerated(Schema.DatabaseGeneratedOption.Identity)> _
Public Property CodigoAluno() As Integer
<StringLength(50)> _
<Required(ErrorMessage:="O nome do Aluno é obrigatório!")> _
Public Property Nome() As String
<StringLength(100)> _
<Required(ErrorMessage:="O Email do aluno é obrigatório!")> _
Public Property Email() As String
Public Property Curso() As Curso
End Class
|
Agora temos que criar a classe do contexto a qual é derivada da classe DBContext com duas propriedades DbSet, uma para Aluno e outra para Curso:
2 - No menu PROJECT selecione Add Class e informe o nome ContextoCurso.vb e clique no botão Add;
Imports System.Data.Entity
Public Class ContextoCurso
Inherits DbContext
Public Sub New()
MyBase.New("EscolaMacoratti")
End Sub
Public Property CursosMacoratti() As DbSet(Of Curso)
Public Property AlunosMacoratti() As DbSet(Of Aluno)
End Class
|
O construtor Sub New() define o banco de dados que será criado e os métodos definem os nomes das tabelas geradas.
Agora vamos abrir o arquivo Module1.vb e definir o código que ira usar as entidades do modelo. Vamos criar um curso, um aluno e persistir as informações no banco de dados usando o código a seguir:
Module Module1
Sub Main()
Using contextoCurso As New ContextoCurso()
'criando um novo curso e incluindo no contexto
Dim curso As New Curso() With {.Nome = "Entity Framework", .EscolaId = 100}
contextoCurso.CursosMacoratti.Add(curso)
'criando um novo aluno e incluind no contexto
Dim macoratti As New Aluno() With
{
.Nome = "Jose Carlos Macoratti",
.Curso = curso,
.Email = "macoratti@yahoo.com"
}
contextoCurso.AlunosMacoratti.Add(macoratti)
'persistindo as informações no banco de dados
Dim registros As Integer = contextoCurso.SaveChanges()
'exibindo uma informação no console e dando uma pausa
Console.WriteLine(" Registros afetados : " + registros)
Console.ReadKey()
End Using
End Sub
End Module
|
Pressione F5 para executar a aplicação. Após alguns segundos você deverá ver a janela de console exibindo a informação de registros afetados igual a 3 :
Neste caso as
informações do curso e do aluno foram armazenadas no banco de dados e tabelas.
Mas onde esta o banco de dados e quais são as tabelas e suas colunas ?
Esta é a beleza do Code First. Ele cria o banco de dados com base no parâmetro
passado no construtor base da sua classe de contexto.
E como nós definimos algumas convenções ele vai gerar o banco de dados
EscolaMacoratti no SQL Server LocalDB local.
A API Code-First
também vai criar duas tabelas no banco de dados,
AlunosMacoratti e CursosMacoratti.
A API cria também a chave primária na tabela conforme as convenções definidas e
também irá criar as outras colunas com o mesmo nome e tipo de dados que foram
definidos nas classes POCO.
Abrindo a janela DataBase Explorer podemos conferir tudo isso :
Vejamos agora o conteúdo das tabelas no DataBase Explorer:
1- Tabela AlunosMacoratti
2- Tabela CursosMacoratti
Vemos assim que os dados foram persistidos nas tabelas geradas.
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.
Violando as regras
Agora vamos realizar uma validação das regras definidas violando-as de propósito. Para isso vamos criar uma rotina no módulo Main() chamada violandoAsRegras() com o código abaixo:
Private Sub violandoAsRegras()
Try
Using contextoCurso As New ContextoCurso()
Dim curso As New Curso() With {.Nome = "", .EscolaId = 100} 'nome do curso vazio
contextoCurso.CursosMacoratti.Add(curso)
Dim macoratti As New Aluno() With
{
.Nome = "", .Curso = curso, .Email = "" 'nome e Email do aluno vazios
}
contextoCurso.AlunosMacoratti.Add(macoratti)
Dim registros As Integer = contextoCurso.SaveChanges()
Console.WriteLine("Registros afetados :" + registros.ToString())
Console.ReadKey()
End Using
Catch ex As DbEntityValidationException
For Each falha In ex.EntityValidationErrors
Console.WriteLine("Falha na validação : " & falha.Entry.Entity.[GetType]().ToString)
For Each erro In falha.ValidationErrors
Console.WriteLine(erro.PropertyName + " " + erro.ErrorMessage)
Next
Next
Console.ReadKey()
End Try
End Sub
|
Para tratar as exceções estamos usando a classe DbEntityValidationException e exibindo os erros no Console. E para isso temos que usar o namespace Imports System.Data.Entity.Validation.
Executando o projeto e chamando a rotina a partir de Main() teremos o seguinte resultado:
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.
A inicialização do Banco de dados
A figura a seguir mostra o fluxo de trabalho de inicialização do banco de dados com base no parâmetro passado no construtor base da classe de contexto que é derivado de DbContext:
Note que podemos passar os seguintes parâmetros no construtor da classe base:
Além disso existem quatro estratégias de inicialização do banco de dados :
Você também pode desabilitar o inicializar em sua aplicação. Para isso basta definir incluir a linha em azul construtor da classe de contexto conforme o código abaixo:
Public Sub New()
MyBase.New("EscolaMacoratti")
Entity.Database.SetInitializer(Of ContextoCurso)(Nothing)
End Sub
|
Você pode também
inserir dados em suas tabelas de banco de dados no processo de inicialização
do banco de dados. Isso vai ser importante se você deseja fornecer alguns dados
de teste para a sua aplicação ou para fornecer alguns dados mestre padrão para
sua aplicação.
Para alimentar dados em seu banco de dados, você tem que criar um inicializador
personalizado . Abaixo temos um exemplo de como você pode fornecer dados padrão
para a tabela Curso durante a inicialização do banco de dados primário:
Public Class CursoInitializer
Inherits DropCreateDatabaseAlways(Of ContextoCurso)
Protected Overrides Sub Seed(context As ContextoCurso)
Dim defaultStandards As IList(Of Curso) = New List(Of Curso)()
defaultStandards.Add(New Curso() With { _
.Nome = "Curso 1", _
.EscolaId = 500
})
defaultStandards.Add(New Curso() With { _
.Nome = "Curso 1", _
.EscolaId = 600
})
defaultStandards.Add(New Curso() With { _
.Nome = "Curso 3", _
.EscolaId = 700
})
For Each cur As Curso In defaultStandards
context.CursosMacoratti.Add(cur)
Next
MyBase.Seed(context)
End Sub
End Class
|
Depois dessa apresentação só nos resta dizer: Muito prazer Code-First.
Na próxima aula vamos tratar da abordagem Model-First no Entity Framework.
Veja os
Destaques e novidades do SUPER DVD Visual Basic
(sempre atualizado) : clique e confira !
Quer migrar para o VB .NET ?
Quer aprender C# ??
|
Gostou ?
Compartilhe no Facebook
Compartilhe no Twitter
Referências: