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:

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
End Interface

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;
as classes POCO não são obrigadas a herdar de nenhuma outra classe ou implementar nenhuma interface.Portanto as classes POCO  são independente de frameworks.

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:
  • 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

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 :

  1. CreateDatabaseIfNotExists: Este é inicializador padrão. Como o nome sugere, ele vai criar o banco de dados se não existe de acordo com a configuração. No entanto, se você alterar a classe de modelo e, em seguida, executar o aplicativo com este inicializador, então ele irá lançar uma exceção.
  2. DropCreateDatabaseIfModelChanges: Este inicializador exclui o banco de dados existente e cria um novo banco de dados se suas classes de modelo(classes de entidade) forem alteradas. Assim você não precisa se preocupar com a manutenção de seu esquema de banco de dados quando o seu modelo mudar.
  3. DropCreateDatabaseAlways: Como o nome sugere, este inicializador exclui o banco de dados cada vez que você executar o aplicativo, independentemente de suas classes de modelo mudarem ou não. Isso é útil quando você quer um novo banco de dados a cada vez que você executar o aplicativo durante o desenvolvimento.
  4. Inicializador Personalizado: Você também pode criar o seu próprio inicializador, se alguma das situações acima não satisfaz sua necessidade ou se você quiser fazer algum outro processo, quando inicializar o 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:


José Carlos Macoratti