Por que eu devo ler este artigo:A necessidade de transformar informações oriundas de bases relacionais em objetos corresponde, sem sombra de dúvidas, a um tipo de ocorrência extremamente comum no desenvolvimento de aplicações. Diversos frameworks foram criados levando em consideração tal aspecto, sendo o Entity Framework uma das soluções oferecidas pela Microsoft para atender a demandas deste gênero.

Este artigo tem por meta apresentar o Entity Framework Code First (uma das alternativas para uso do Entity Framework), demonstrando como este recurso pode ser útil na implementação de soluções dentro da plataforma .NET.

Para isto, será criada uma aplicação de testes que fará uso de um mecanismo de CRUD genérico e reutilizável baseado no Entity Framework.

É inegável a importância que os bancos de dados relacionais adquiriram dentro das mais diversas organizações. Oferecendo meios para o armazenamento de um volume crescente de informações num formato estruturado, estes repositórios são utilizados como recursos na automação de processos cotidianos por empresas e órgãos governamentais dos mais variados portes.

Importante destacar ainda a existência de inúmeras opções baseadas neste tipo tecnologia, desde produtos comercializados por gigantes da área de software (como o SQL Server da Microsoft, o DB2 da IBM e o Oracle), passando ainda por soluções livres mantidas por comunidades de desenvolvedores (como o Firebird e o PostgreSQL).

saiba mais Saiba mais sobre Entity Framework code-first

Como não é difícil de imaginar, as principais plataformas de desenvolvimento da atualidade oferecem total suporte ao uso de bancos de dados relacionais na construção de novos sistemas. Com o .NET Framework isto não é diferente: a tecnologia conhecida como ADO.NET (ActiveX Data Object for .NET) existe desde os primórdios desta plataforma, representando a base para que aplicações em .NET consigam interagir com serviços de bancos de dados como o SQL Server.

Por mais que o ADO.NET conte com recursos capazes de atender às mais diversas demandas decorrentes da implementação de projetos em .NET, este mecanismo apresenta algumas limitações. Por não dispor de um recurso nativo capaz de converter as informações de uma base relacional em construções típicas da orientação a objetos, é bastante comum que desenvolvedores façam uso de algum dispositivo responsável por realizar estas transformações.

O processo de mapeamento das estruturas relacionais em objetos compatíveis com uma linguagem/plataforma é conhecido como ORM, com o uso de frameworks que definem este tipo de comportamento estando normalmente associado à implementação de funcionalidades de CRUD.

Introduzido a com a versão 3.5 da plataforma .NET, o Entity Framework é uma solução ORM criada pela Microsoft com o intuito de simplificar a construção de aplicações que façam uso de bancos de dados relacionais.

Um dos grandes diferenciais do Entity Framework foi permitir a criação de modelos de bancos de dados por meio de uma ferramenta gráfica, dispensando os desenvolvedores da necessidade de codificar uma ou mais classes representando as estruturas de uma base específica.

Muito embora esta técnica apresente vantagens (como a geração de um diagrama com todas as classes que efetuam o mapeamento objeto-relacional), com o release da versão 4.1 do Entity Framework (voltado ao .NET Framework 4.0) uma nova alternativa de desenvolvimento foi disponibilizada. Trata-se do Entity Framework Code First, em que classes são codificadas de forma a representar estruturas pertencentes a um banco de dados utilizado pela aplicação construída.

O objetivo deste artigo é discutir as principais características do Code First. A partir disto será apresentado um exemplo de uso deste recurso em uma aplicação MVC, empregando neste caso um mecanismo genérico baseado no do Entity Framework Code First para a implementação de funções de CRUD.

Entity Framework Code First: uma visão geral

Na primeira versão do Entity Framework a manipulação de informações provenientes de bases relacionais acontecia a partir de um modelo gráfico gerado por meio do Visual Studio. Em termos práticos, tal modelo nada mais é do que um arquivo XML com a extensão .edmx, em que Views, tabelas e os relacionamentos entre essas estruturas são mapeados para classes que poderão ser utilizadas em aplicações .NET.

Quanto à codificação de instruções acessando e manipulando as entidades de um modelo, será a classe ObjectContext (namespace System.Data.Objects) a estrutura responsável por prover as funcionalidades que tornam possíveis estas ações.

Ao criar um modelo baseado em um arquivo .edmx, duas abordagens de desenvolvimento empregando o Entity Framework são possíveis:

· Database First: através de uma ferramenta de engenharia reversa um modelo é gerado, tomando-se como referência as informações de metadados de um banco relacional pré-existente;

· Model First: as diferentes classes representando estruturas de uma base relacional são modeladas antes mesmo que tais construções tenham sido implementadas. O Entity Framework disponibiliza recursos para a criação dos elementos gerados em um banco de dados de destino.

Na Figura 1 está um exemplo de arquivo .edmx do Entity Framework. É possível observar neste modelo a presença de entidades representando as tabelas de regiões e estados de uma base de dados hipotética.

Exemplo de modelo do
Entity Framework gerado através do Visual Studio
Figura 1. Exemplo de modelo do Entity Framework gerado através do Visual Studio

Independentemente da escolha que for feita, algumas questões devem ser ponderadas quanto ao uso do Entity Framework via arquivos .edmx:

· A criação de um modelo baseado em um arquivo .edmx é recomendável para sistemas novos, em que classes representando diferentes itens de negócio sequer foram implementadas;

· Embora represente uma alternativa que pode conduzir a uma maior produtividade, este processo de modelagem gráfica baseado em um arquivo XML pode revelar-se como inviável em algumas situações.

Um destes casos seria o de projetos com grandes equipes, nos quais diversos programadores podem estar editando um mesmo arquivo .edmx e adicionando/modificando estruturas dentro deste último.

A unificação de tais modificações (atividade esta popularmente conhecida como “merge”) poderá resultar na perda de alguns ajustes ou, até mesmo, na danificação do modelo .edmx (algo que obrigaria a um retrabalho a fim de regerar e configurar manualmente esta estrutura);

· Modificações na base de dados podem também invalidar as representações definidas em um arquivo .edmx. Como também constam neste último informações de metadados (como o tamanho e o tipo de campos, o relacionamento entre diferentes tabelas etc.), alterações na estrutura de um banco de dados exigirão muitas vezes a atualização do modelo correspondente;

· Cada uma das classes pertencentes a um modelo gerado pelo Visual Studio herdará do tipo EntityObject (namespace System.Data.Objects.DataClasses). Essa estrutura encapsula todo um mecanismo para detecção a de mudanças nos valores de uma entidade/objeto, sendo que muitos desenvolvedores não vêm com bons olhos (já que em muitos cenários um objeto comum com propriedades e, eventualmente, alguns métodos simples resolveriam as demandas envolvendo a manipulação de valores referentes a um ou mais ou registros de uma base).

Por mais que as primeiras versões do Entity Framework contassem com todos os recursos normalmente esperados de um mecanismo de ORM, muitos desenvolvedores ainda preferiam o uso de soluções que dispensassem a modelagem gráfica de classes representando entidades de negócio.

A disponibilização da abordagem Code First a partir do release 4.1 do Entity Framework (voltada ao .NET Framework 4.0) procurou atender a esta demanda, contemplando ainda todas as funcionalidades de manipulação de dados suportadas pelos métodos Model e Database First.

Conforme mencionado anteriormente, dentro do padrão Code First a representação de estruturas como tabelas e views é feita por meio de classes que não possuem nenhuma dependência direta para com o Entity Framework (logo, não há herança a partir de ObjectEntity).

Estes tipos seguem um pattern conhecido como POCO (BOX 1), sendo que esta característica permite inclusive a utilização da abordagem Code First com classes já implementadas anteriormente em um projeto.

BOX 1. POCO

POCO (sigla do inglês “Plain Old CRL Objects”) é um padrão em que classes são definidas com uma estrutura simples, codificadas de forma a não possuir uma ligação direta com qualquer tipo de framework ORM. Devido a esta independência para com mecanismos de acesso a dados, tipos que seguem este padrão podem ser reutilizados com diferentes bibliotecas na manipulação de informações provenientes de bases relacionais.

Na Listagem 1 é possível observar exemplos de classes que podem ser utilizadas em conjunto com o Entity Framework Code First. Neste caso específico, as classes Regiao e Estado contam com propriedades cujos nomes e tipos coincidem com as definições da base de dados. Além disso, nota-se a existência de um relacionamento entre Estado e Regiao (a propriedade Regiao, que está declarada na linha 17).

Listagem 1. As classes Regiao e Estado

  01 using System;
  02
  03 namespace Exemplo
  04 {
  05     public class Regiao
  06     {
  07         public string CdRegiao { get; set; }
  08         public string NmRegiao { get; set; }
  09     }
  10
  11     public class Estado
  12     {
  13         public string CdEstado { get; set; }
  14         public string NmEstado { get; set; }
  15         public string CdRegiao { get; set; }
  16         public virtual Regiao Regiao { get; set; }
  17     }
  18 }

Ao desenvolver tomando como base o padrão Code First, a Microsoft recomenda o uso da classe DbContext (namespace System.Data.Entity) ao invés do tipo ObjectContext. Esta estrutura foi também introduzida a partir do Entity Framework 4.1, sendo uma versão simplificada que encapsula as funcionalidades de ObjectContext.

Será a partir de uma instância da classe DbContext que acontecerá o acesso a uma base de dados para a realização de operações de consulta, inclusão, alteração e exclusão de informações.

Estas ações serão sempre executadas sobre um conjunto de entidades, as quais são representadas através de propriedades do tipo genérico DbSet<Entity> (namespace System.Data.Entity) declaradas em classes que derivam de DbContext.

Em tempo de execução o Entity Framework Code First irá analisar classes que representem entidades, sendo capaz de inferir relacionamentos e os tipos de dados utilizados por estas construções.

Contudo, haverá casos em que os desenvolvedores precisarão especificar maiores detalhes envolvendo a configuração destes tipos, a fim de possibilitar que as funcionalidades de ORM do Code First façam o correto mapeamento entre tais elementos e as estruturas de um banco de dados. Existem duas opções para realizar estas configurações:

· Através da utilização de atributos conhecidos como Data Annotations. Estes itens encontram-se definidos no namespace System.ComponentModel.DataAnnotations, podendo ser aplicados às declarações de classes e propriedades utilizadas em conjunto com o padrão Code First.

Isto não significa, no entanto, que o uso de Data Annotations seja uma característica específica do Entity Framework; o ASP.NET MVC representa um bom exemplo de framework que faz uso deste mecanismo, valendo-se de tais atributos como base para a validação de dados em telas de cadastros;

· Empregando o mecanismo do Entity Framework Code First conhecido como Fluent API. Este recurso faz uso da classe genérica EntityTypeConfiguration<TEntityType> (namespace System.Data.Entity.ModelConfiguration), a qual disponibiliza vários métodos de configuração que se baseiam em um padrão conhecido como interface fluente.

A chamada aos métodos da Fluent API deve acontecer a partir de uma versão sobrecarregada do método OnModelCreating, em uma classe derivada do tipo DbContext.

Na Listagem 2 é apresentado um exemplo de implementação do tipo DbContext, com a configuração das classes Regiao e Estado sendo feita por meio do uso da Fluent API. Observando a forma como o tipo TesteDbContext foi estruturado, é possível observar:

· As propriedades Regioes e Estados (linhas 9 e 10). Estes elementos armazenarão instâncias do tipo genérico DbSet<Entity>, representando conjunto de entidades dos tipos Regiao e Estado.

O preenchimento destas propriedades acontecerá de forma automática, com o pré-requisito para isto sendo a existência de uma string de conexão com o mesmo nome da classe TesteDbContext declarada no arquivo de configuração da aplicação;

· Uma versão sobrecarregada do método OnModelCreating (linha 12). Esta operação recebe como parâmetro uma instância do tipo DbModelBuilder (namespace System.Data.Entity); a partir deste objeto será acessado o método genérico Entity, o qual retornará uma referência do tipo EntityTypeConfiguration .

Será por meio de instâncias da classe EntityTypeConfiguration que métodos como ToTable e HasKey serão acionados, a fim de gerar as configurações que permitirão o correto mapeamento entre tipos e estruturas de bancos de dados manipuladas pelo tipo derivado derivado de DbContext.

Listagem 2. Exemplo de uso do tipo DbContext

  01 using System;
  02 using System.Data.Entity;
  03 using System.Data.Entity.ModelConfiguration.Configuration;
  04
  05 namespace Exemplo
  06 {
  07     public class TesteDbContext : DbContext
  08     {
  09         public DbSet<Regiao> Regioes { get; set; }
  10         public DbSet<Estado> Estados { get; set; }
  11
  12         protected override void OnModelCreating(
  13             DbModelBuilder modelBuilder)
  14         {
  15             modelBuilder.Entity<Regiao>()
  16                 .ToTable("TB_REGIAO")
  17                 .HasKey(r => r.CdRegiao);
  18             modelBuilder.Entity<Estado>()
  19                 .ToTable("TB_ESTADO")
  20                 .HasKey(r => r.CdEstado);
  21         }
  22     }
  23 }

Um último ponto que merece ser destacado diz respeito às formas possíveis de interação do Entity Framework Code First com bancos de dados do SQL Server. Além do acesso para a execução de comandos que envolvam consultas, inclusão, alteração e/ou exclusão de dados, o Code First oferece ainda funcionalidades para a criação de um banco de dados a partir das classes (e respectivas configurações) que representam diferentes entidades dentro de um sistema.

Este recurso pode ser extremamente útil em aplicações que funcionem como provas de conceito, em que as diferentes funcionalidades foram concebidas com o objetivo de demonstrar uma arquitetura elaborada com um fim específico.

Exemplo de utilização de Entity Framework Code First

Para implementar a solução demonstrada foram utilizados os seguintes recursos:

· O Microsoft Visual Studio Professional 2013 Update 1 como IDE de desenvolvimento;

· O .NET Framework 4.5.1;

· O SQL Server 2012 como SGBD;

· O framework ASP.NET MVC 5.1 como base para a criação das interfaces gráficas de testes;

· A versão 6.0.2 do Entity Framework;

· A biblioteca jQuery, além de plug-ins baseados nesta última (autoNumeric e jQuery.MaskedInput);

· O Unity Application Block 3.0 como mecanismo de injeção de dependências, bem como a extensão Unity bootstrapper for ASP.NET MVC.

Será através do utilitário NuGet que as bibliotecas do Entity Framework, do Unity Application Block, da extensão Unity bootstrapper for ASP.NET MVC, assim como os plugins autoNumeric e jQuery.MaskedInput serão adicionados aos projetos que farão uso dos mesmos.

O exemplo apresentado a seguir tem por objetivo abordar a construção de uma aplicação para o gerenciamento de consultores vinculados a uma consultoria de tecnologia.

Para isto serão implementadas funcionalidades de visualização, inclusão, alteração e exclusão de registros contendo dados de profissionais que possuam contrato junto à companhia em questão.

Importante destacar, antes de prosseguir com a implementação da solução de exemplo, que um dos projetos que fará parte desta última é uma Class Library com funcionalidades básicas de CRUD.

Esta biblioteca demonstrará como os recursos do Entity Framework Code First podem ser utilizados na criação de um mecanismo genérico, o qual possibilitará a manipulação de informações oriundas de bancos relacionais de uma forma simplificada.

Geração da Base de Dados

A solução que estará sendo detalhada a partir da próxima seção fará uso de um banco de dados chamado TesteCodeFirst. No link para download do material deste artigo encontra-se o script SQL para criação de tal base. O banco de dados TesteCodeFirst será formada pelas tabelas:

· TB_OCUPACAO: nesta estrutura constarão as ocupações/cargos possíveis dentro de uma consultoria hipotética;

· TB_PROFISSIONAL: tabela que conterá informações de profissionais que possuam contrato junto à empresa de tecnologia (além de suas respectivas ocupações).

Criando a solução de exemplo

A fim de iniciar a construção da aplicação de testes proposta por este artigo, será necessário criar primeiramente uma Solution no Visual Studio chamada TesteCodeFirst. A solução mencionada será formada pelos seguintes projetos:

· TesteCodeFirst.Utils.ORM: biblioteca em que estão definidas funcionalidades básicas de CRUD, utilizando para isto recursos do Entity Framework Code First;

· TesteCodeFirst.Entities: nesta Class Library constarão as classes que representam entidades de negócio manipuladas pela solução de exemplo;

· TesteCodeFirst.DAL: projeto no qual serão implementadas as classes de acesso a dados, empregando para isto o mecanismo de mapeamento objeto-relacional definido na biblioteca TesteCodeFirst.Utils.ORM;

· TesteCodeFirst.BLL: Class Library em que estarão classes responsáveis por invocar a camada de acesso a dados, assim como as regras de negócio da solução;

· TesteCodeFirst.MVC: aplicação MVC através na qual serão implementadas as funcionalidades que envolvem a manipulação de dados cadastrais de profissionais de TI.

Implementando as classes básicas de acesso a dados

TesteCodeFirst.Utils.ORM é um projeto do tipo Class Library em que irão constar as classes básicas de acesso a dados utilizadas pela Solution TesteCodeFirst. Tais tipos estarão sendo criados com o intuito de simplificar a interação com a base de dados de testes, através da construção de um mecanismo de CRUD genérico.

A implementação destas funcionalidades de uso geral empregará, por sua vez, recursos do Entity Framework Code First.

Com o projeto TesteCodeFirst.Utils.ORM já criado, o primeiro ajuste a ser efetuado nesta Class Library consistirá na inclusão via NuGet dos assemblies do Entity Framework e do Unity Application Block (conforme indicado na Figura 2).

Configurando as
dependências do projeto
Figura 2. Configurando as dependências do projeto TesteCodeFirst.Utils.ORM

Quanto aos tipos que farão parte da Class Library TesteCodeFirst.Utils.ORM, serão implementadas nesta seção as seguintes estruturas:

· A interface IUnitOfWork;

· A classe abstrata BaseRepository.

Na Listagem 3 está a definição do tipo IUnitOfWork. Esta interface está baseada em um padrão chamado Unit of Work (BOX 2), sendo utilizada no acesso e manipulação de informações provenientes de bancos relacionais.

Analisando a interface de IUnitOfWork, é possível observar:

· A existência da propriedade DefaultContext (linha 8), à qual estará associada uma instância do tipo DbContext. Será através desta referência que o mecanismo genérico de CRUD (a partir da classe BaseRepository) irá realizar operações de consulta, inclusão, alteração e/ou exclusão em uma base de dados;

· O método Commit (linha 9), no qual deverão constar instruções que confirmem (persistam) um conjunto de alterações na base de dados vinculada à propriedade DefaultContext;

· O tipo IUnitOfWork está derivando da interface IDisposable (namespace System). Classes baseadas nesta estrutura (IDisposable) deverão implementar um método chamado Dispose, o qual será acionado automaticamente pelo Garbage Collector a fim de liberar recursos alocados em memória.

Listagem 3. Interface IUnitOfWork

  01 using System;
  02 using System.Data.Entity;
  03
  04 namespace TesteCodeFirst.Utils.ORM
  05 {
  06     public interface IUnitOfWork : IDisposable
  07     {
  08         DbContext DefaultContext { get; }
  09         void Commit();
  10     }
  11 }


BOX 2. O padrão Unit of Work

O pattern Unit of Work tem por objetivo manter o registro de tudo o que é alterado numa base de dados durante o processamento de um grupo de operações. Para isto, utiliza-se uma mesma transação durante uma série de interações, podendo-se ao final confirmar a mesma a fim de persistir as informações na base de dados (através do método Commit no exemplo aqui descrito).

Já na Listagem 4 é apresentada a implementação da classe abstrata BaseRepository. Nessa estrutura estarão centralizadas todas as funcionalidades básicas de CRUD, sendo que este tipo deverá ser implementado por classes concretas que representem objetos de acesso a dados.

Quanto à estrutura do tipo BaseRepository, é necessário destacar:

· A presença de uma restrição (“where T : class” – linha 13) utilizada para definir que somente classes que possuam um construtor sem parâmetros (implementado explicitamente ou não) poderão ser indicadas no lugar do placeholder <T> (linha 12). Essa instrução nada mais é do que um recurso de Generics conhecido como constraint, sendo que através do mesmo é possível estipular condições que devam ser atendidas pelo tipo T;

· A propriedade UnitOfWork, que armazenará uma referência de um objeto cuja classe correspondente implementa a interface IUnitOfWork (linha 16). Esta propriedade foi marcada com o atributo DependencyAttribute (namespace Microsoft.Practices.Unity) para que seja preenchida automaticamente em tempo de execução, fazendo-se uso para isto do mecanismo de injeção de dependências disponibilizado pelo Unity Application Block.

O objetivo deste procedimento é garantir que todos o tipos derivados de BaseRepository apontem para uma única instância de IUnitOfWork (no caso específico desta solução de exemplo, durante uma requisição HTTP), garantindo assim que diversas ações envolvendo modificações numa base permaneçam dentro de uma única transação;

· O método privado GetKeyNames (linha 20). Esta operação irá retornar os nomes dos campos que fazem parte da chave primária de uma entidade representada pelo tipo T, sob a forma de um array de strings associado ao atributo privado “_keyNames” (declarado na linha 18).

Caso o conteúdo da referência “_keyNames” seja nulo, uma nova instância do tipo genérico ObjectSet é gerada (linha 24), acessando-se para isto o objeto ObjectContext (este último uma referência de IObjectContextAdapter, interface que pertence ao namespace System.Data.Entity.Infrastructure) por meio da propriedade DefaultContext definida em IUnitOfWork e, na sequência, o método genérico CreateObjectSet.

Através da instância do tipo ObjectSet serão acessadas as propriedades EntitySet, ElementType e KeyMembers; a partir deste último elemento e do método Select será criado um novo array com os identificadores dos campos que fazem parte de uma chave primária, com o resultado disto sendo então associado ao atributo “_keyNames”;

· A operação privada GetPrimaryKeyValues (linha 34). Recebendo como parâmetro uma instância de um tipo representado por T, este método produzirá como resultado de sua execução um array de objetos com os valores dos campos de chave-primária para a referência informada durante a sua invocação.

O funcionamento desta operação envolve num primeiro momento uma chamada ao método GetKeyNames (descrito anteriormente), com o intuito de obter o nome dos campos que formam a chave primária (linha 36). Além disso, uma instância da classe Type (namespace System) é gerada, de forma a possibilitar o acesso a informações de metadata do tipo T (linha 37).

Um novo array de objetos é então criado (linha 39), preenchido com os valores dos campos de chave primária (dentro de um loop que se inicia na linha 40, no qual são acessadas as duas referências geradas no início da execução de GetPrimaryKeyValues) e finalmente devolvido como resultado (linha 46);

· O método público FindByPrimaryKey (linha 49). Esta operação recebe como parâmetro um array com os valores que identificam um determinado registro. Partindo do objeto associado à propriedade DefaultContext de IUnitOfWork, é acionado o método genérico Set e, em seguida, a operação Find (repassando a esta última o parâmetro informado ao acionar FindByPrimaryKey).

A instância do tipo T devolvida pelo método Find será retornada então como resultado, correspondendo a um registro que está em conformidade com os valores de chave primária fornecidos à operação FindByPrimaryKey;

· A operação pública GetQueryable (linha 55). Ao ser invocado, este método acionará a operação Set a partir da propriedade DefaultContext de um objeto do tipo IUnitOfWork, produzindo como resultado uma instância baseada na interface genérica IQueryable (namespace System.Linq). Objetos cuja classe deriva de IQueryable servem de base para a execução de consultas em um banco de dados, empregando para tanto recursos do Entity Framework e da extensão LINQ;

· Os métodos públicos Insert (linha 60) e Delete (linha 65). Estas operações possuem um comportamento bastante similar, acessando o método Set por meio do objeto associado à propriedade DefaultContext de IUnitOfWork. A partir de chamadas às operações Add e Remove da instância retornada pelo método Set acontecerá, respectivamente, a inclusão e a exclusão de registros representados pelo tipo T;

· A operação pública Update (linha 70). Utilizado na atualização de um registro que corresponda ao tipo T, o método Update aciona primeiramente as operações GetPrimaryKeyValues (linha 72) e FindByPrimaryKey (linha 73). Se não for encontrado um registro válido na base, uma exceção será lançada descrevendo este problema (linha 76).

Caso o objeto em questão conte com um registro equivalente no banco de dados, o método Entry é acionado a partir do objeto vinculado à propriedade DefaultContext (linha 83); esta ação retornará um referência do tipo genérico DbEntityEntry (namespace System.Data.Entity.Infrastructure.DbEntityEntry). Partindo desta última instância, será acionado o método SetValues a partir da propriedade CurrentValues (linha 85), fornecendo como parâmetro a instância informada inicialmente à operação Update; este procedimento tem por objetivo atualizar os diferentes campos que formam um registro.

Por fim, a propriedade State da instância do tipo DbEntityEntry é atualizada com o valor de enumeration EntityState.Modified, a fim de marcar o registro da classe T como alterado para posterior persistência das modificações.

Listagem 4. Classe BaseRepository

  01 using System;
  02 using System.Collections.Generic;
  03 using System.Linq;
  04 using System.Data.Entity;
  05 using System.Data.Entity.Infrastructure;
  06 using System.Data.Entity.Core.Objects;
  07 using System.ComponentModel.DataAnnotations;
  08 using Microsoft.Practices.Unity;
  09
  10 namespace TesteCodeFirst.Utils.ORM
  11 {
  12     public abstract class BaseRepository<T>
  13         where T : class
  14     {
  15         [Dependency]
  16         public IUnitOfWork UnitOfWork { get; set; }
  17
  18         private string[] _keyNames;
  19
  20         private string[] GetKeyNames()
  21         {
  22             if (_keyNames == null)
  23             {
  24                 ObjectSet<T> objectSet =
  25                     ((IObjectContextAdapter)UnitOfWork.DefaultContext)
  26                         .ObjectContext.CreateObjectSet<T>();
  27                 _keyNames = objectSet.EntitySet.ElementType.
  28                     KeyMembers.Select(k => k.Name).ToArray();
  29             }
  30
  31             return _keyNames;
  32         }
  33
  34         private object[] GetPrimaryKeyValues(T item)
  35         {
  36             var keyNames = GetKeyNames();
  37             Type type = typeof(T);
  38
  39             object[] keys = new object[keyNames.Length];
  40             for (int i = 0; i < keyNames.Length; i++)
  41             {
  42                 keys[i] = type.GetProperty(keyNames[i])
  43                     .GetValue(item, null);
  44             }
  45
  46             return keys;
  47         }
  48
  49         public T FindByPrimaryKey(params object[] keyValues)
  50         {
  51             return UnitOfWork.DefaultContext.Set<T>()
  52                 .Find(keyValues);
  53         }
  54
  55         public IQueryable<T> GetQueryable()
  56         {
  57             return UnitOfWork.DefaultContext.Set<T>();
  58         }
  59
  60         public void Insert(T entity)
  61         {
  62             UnitOfWork.DefaultContext.Set<T>().Add(entity);
  63         }
  64
  65         public void Delete(T entity)
  66         {
  67             UnitOfWork.DefaultContext.Set<T>().Remove(entity);
  68         }
  69
  70         public void Update(T entity)
  71         {
  72             object[] keyValues = this.GetPrimaryKeyValues(entity);
  73             T currentEntity = this.FindByPrimaryKey(keyValues);
  74             if (currentEntity == null)
  75             {
  76                 throw new Exception(String.Format(
  77                     "Erro durante a atualização de uma instância " +
  78                     "do tipo . Verifique se o registro " +
  79                     "correspondente existe na base de dados.",
  80                     typeof(T).Name));
  81             }
  82
  83             var entry = UnitOfWork.DefaultContext
  84                 .Entry(currentEntity);
  85             entry.CurrentValues.SetValues(entity);
  86             entry.State = EntityState.Modified;
  87         }
  88     }
  89 }

Implementando as entidades

No projeto TesteCodeFirst.Entities serão definidas as representações das diferentes entidades manipuladas pela aplicação de testes. Estas classes servirão de base para a transferência de dados entre as diferentes camadas da solução aqui apresentada, assim como farão parte do modelo baseado no Entity Framework Code First descrito mais adiante. Constarão nesta Class Library os seguintes tipos:

· Ocupacao;

· ResumoDadosProfissional;

· Profissional.

Na Listagem 5 está a definição da classe Ocupacao, a qual será utilizada na exibição das diferentes funções possíveis de se atribuir a um profissional. Analisando a forma como este tipo foi implementado, é possível observar:

· O uso do Data Annotation TableAttribute (namespace System.ComponentModel.DataAnnotations.Schema) na linha 7. Quando associado a uma classe, este atributo indica a tabela à qual tal construção estará vinculada (para o caso específico do tipo Ocupacao, o mesmo corresponde a uma representação da tabela TB_OCUPACAO);

· A propriedade Id (linha 11), que foi marcada com o atributo KeyAttribute (namespace System.ComponentModel.DataAnnotations). Este Data Annotation deverá ser vinculado a propriedade que correspondam aos campos de chave-primária em uma tabela (que neste exemplo seria a propriedade Id);

· A propriedade NomeOcupacao (linha 13).

Listagem 5. Classe Ocupacao

  01 using System;
  02 using System.ComponentModel.DataAnnotations;
  03 using System.ComponentModel.DataAnnotations.Schema;
  04
  05 namespace TesteCodeFirst.Entities
  06 {
  07     [Table("TB_OCUPACAO")]
  08     public class Ocupacao
  09     {
  10         [Key]
  11         public int? Id { get; set; }
  12         
  13         public string NomeOcupacao { get; set; }
  14     }
  15 }

Nota: Será necessário adicionar uma referência à biblioteca System.ComponentModel.DataAnnotations dentro do projeto TesteCodeFirst.Entities, possibilitando o uso de Data Annotations pelas classes que farão parte desta Class Library.

Já o código referente à classe ResumoDadosProfissional é apresentado na Listagem 6. Embora não possua uma relação direta com o modelo do Entity Framework a ser implementado na próxima seção, este tipo será empregado na exibição de informações relativas aos diferentes profissionais cadastrados na base de dados. Fazem parte da classe ResumoDadosProfissional os seguintes campos:

· Id/Código do profissional (linha 7);

· CPF (linha 8);

· Nome do profissional (linha 9);

· Descrição da ocupação (linha 10);

· Valor recebido por hora (linha 11);

· O valor mensal recebido por um profissional (linha 13) com base em uma média de 168 horas trabalhadas.

Listagem 6. Classe ResumoDadosProfissional

  01 using System;
  02
  03 namespace TesteCodeFirst.Entities
  04 {
  05     public class ResumoDadosProfissional
  06     {
  07         public int Id { get; set; }
  08         public string CPF { get; set; }
  09         public string NmProfissional { get; set; }
  10         public string DsOcupacao { get; set; }
  11         public decimal VlHora { get; set; }
  12
  13         public decimal VlBase168Horas
  14         {
  15             get
  16             {
  17                 return VlHora * 168;
  18             }
  19         }
  20     }
  21 }

Na Listagem 7 está a definição da classe Profissional. Este tipo conta com dados pessoais de um consultor, sua ocupação, o valor recebido por hora trabalhada e informações para contato.

A fim de possibilitar o mapeamento entre propriedades da classe Profissional e campos da tabela TB_PROFISSIONAL, foram realizados os seguintes:

· O tipo Profissional foi marcado com atributo TableAttribute (linha 8), indicando a estrutura correspondente na base de testes;

· À propriedade Id (linha 12) foi associado o atributo KeyAttribute, já que este elemento corresponde ao campo de chave-primária na tabela TB_PROFISSIONAL;

· Já o Data Annotation ForeignKeyAttribute (namespace System.ComponentModel.DataAnnotations.Schema) está vinculado à propriedade CdOcupacao. Este atributo recebe como parâmetro o nome da propriedade que conterá dados de uma ocupação (o elemento Ocupacao, definido na linha 27), indicando assim que o campo CdOcupacao possui uma chave primária.

Conforme pode observar, diversos outros tipos de Data Annotations foram associados às propriedades da classe Profissional.

No caso das propriedades CPF (linha 16), NmProfissional (linha 20), CdOcupacao (linha 25), Email (linha 34), Telefone (linha 38) e VlHora (linha 47), nota-se que a declaração de cada um destes elementos está associada a um atributo RequiredAttribute (namespace System.ComponentModel.DataAnnotations).

Ao efetuar este tipo de ajuste, o ASP.NET MVC irá considerar como obrigatório o preenchimento em tela de campos que foram marcados com o item RequiredAttribute, exibindo ainda uma mensagem (definida na propriedade ErrorMessage) se esta condição não for atendida.

Outro ponto a ser destacado é o uso do atributo DisplayNameAttribute (namespace System.ComponentModel). Além dos campos já mencionados, este tipo também foi vinculado à propriedade DsObservacao (linha 50). Quando empregado em conjunto com o framework MVC, a classe DisplayNameAttribute permite a geração automática de legendas para os diversos campos que farão parte de uma tela de inclusão ou exclusão de registros (valores informados como parâmetro no construtor de DisplayNameAttribute correspondem ao texto de tais legendas).

A propriedade Email foi marcada também com o atributo RegularExpressionAttribute (namespace System.ComponentModel.DataAnnotations), com o intuito de verificar se um e-mail informado em tal campo é aceitável. Procurando determinar se o conteúdo de um campo é realmente válido, o tipo RegularExpressionAttribute faz uso de sequências padronizadas de texto conhecidas expressões regulares.

No que se refere à propriedade VlHora, observa-se também a utilização do tipo RangeAttribute (namespace System.ComponentModel.DataAnnotations). Este tipo é empregado para definir os limites mínimo e máximo de um campo, apresentando ainda uma mensagem de erro caso os valores informados por um usuário não estejam em tal faixa.

O atributo DataTypeAttribute (namespace System.ComponentModel.DataAnnotations) também foi associado à propriedade VlHora, indicando que este campo aceitará apenas valores monetários no seu preenchimento. Dentro de aplicações ASP.NET MVC, o tipo DataTypeAttribute costuma ser utilizado para validar o tipo de dado que poderá ser informado em um campo editável.

Listagem 7. Classe Profissional

  01 using System;
  02 using System.ComponentModel;
  03 using System.ComponentModel.DataAnnotations;
  04 using System.ComponentModel.DataAnnotations.Schema;
  05
  06 namespace TesteCodeFirst.Entities
  07 {
  08     [Table("TB_PROFISSIONAL")]
  09     public class Profissional
  10     {
  11         [Key]
  12         public int? Id { get; set; }
  13
  14         [Required(ErrorMessage = "Campo obrigatório.")]
  15         [DisplayName("CPF")]
  16         public string CPF { get; set; }
  17
  18         [Required(ErrorMessage = "Campo obrigatório.")]
  19         [DisplayName("Nome do Profissional")]
  20         public string NmProfissional { get; set; }
  21
  22         [Required(ErrorMessage = "Campo obrigatório.")]
  23         [ForeignKey("Ocupacao")]
  24         [DisplayName("Ocupação")]
  25         public int? CdOcupacao { get; set; }
  26
  27         public Ocupacao Ocupacao { get; set; }
  28
  29         [Required(ErrorMessage = "Campo obrigatório.")]
  30         [RegularExpression(@"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*" +
  31             @"@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$",
  32             ErrorMessage = "Formato de E-mail inválido.")]
  33         [DisplayName("E-mail para Contato")]
  34         public string Email { get; set; }
  35
  36         [Required(ErrorMessage = "Campo obrigatório.")]
  37         [DisplayName("Telefone para Contato")]
  38         public string Telefone { get; set; }
  39
  40         [Required(ErrorMessage = "Campo obrigatório.")]
  41         [Range(10, 9999.99,
  42             ErrorMessage = "O Valor Hora deve estar entre " +
  43                            "10,00 e 9999,99.")]
  44         [DataType(DataType.Currency,
  45             ErrorMessage = "Valor inválido.")]
  46         [DisplayName("Valor Hora")]
  47         public decimal? VlHora { get; set; }
  48
  49         [DisplayName("Observação")]
  50         public string DsObservacao { get; set; }
  51     }
  52 }

Implementando as classes de acesso a dados

TesteCodeFirst.DAL é um projeto do tipo Class Library no qual estão definidas as seguintes classes (o sufixo “DAO” é uma abreviação de “Data Access Object”, termo em inglês que designa uma classe empregada na geração de objetos de acesso a dados):

· DefaultContext;

· UnitOfWork;

· OcupacaoDAO;

· ProfissionalDAO.

Nota: O projeto TesteCodeFirst.DAL também irá utilizar o Entity Framework. Tal referência poderá ser adicionada por meio do utilitário NuGet.

O tipo DefaultContext (Listagem 8) deriva da classe DbContext, representando o modelo a partir do qual ocorrerá a interação entre a aplicação de exemplo e o banco de dados de testes. Foram definidas nesta estrutura duas propriedades que fazem uso no tipo genérico DbSet, servindo de base para a manipulação de entidades/registros:

· Ocupacoes (linha 9): permite o acesso a dados do cadastro de ocupações disponíveis;

· Profissionais (linha 10): utilizada na manipulação de registros envolvendo informações de profissionais vinculados à consultoria hipotética.

Como aconteceu no caso do tipo TesteDbContext (descrito ainda na seção teórica), as propriedades declaradas em DefaultContext serão preenchidas automaticamente ao gerar uma nova instância desta classe. Além disso, o uso de Data Annotations na codificação das entidades fez com que não fosse mais necessário sobrecarregar o método OnModelCreating.

Listagem 8. Classe DefaultContext

  01 using System;
  02 using System.Data.Entity;
  03 using TesteCodeFirst.Entities;
  04
  05 namespace TesteCodeFirst.DAL.Base
  06 {
  07     public class DefaultContext : DbContext
  08    {
  09         public DbSet<Ocupacao> Ocupacoes { get; set; }
  10         public DbSet<Profissional> Profissionais { get; set; }
  11     }
  12 }

Já a Listagem 9 apresenta o código que define a classe UnitOfWork. Este tipo implementa a interface IUnitOfWork (pertencente ao projeto TesteCodeFirst.DAL), sendo um objeto que centralizará todas as operações de acesso a dados provenientes da base de testes.

Analisando a estrutura da classe UnitOfWork, é possível observar:

· A existência da propriedade DefaultContext (linha 11), a qual representa o modelo do Entity Framework que possibilitará o acesso às informações da base de exemplo. A instância associada a esta propriedade é armazenada no atributo “_defaultContext”, com o preenchimento deste último acontecendo dentro do construtor da classe UnitOfWork (linha 21);

· O método Commit (linha 24). Esta operação utiliza a instância vinculada à propriedade DefaultContext para acionar o método SaveChanges (implementado na classe básica DbContext), a fim de persistir/confirmar um conjunto de modificações no banco de teste;

· O método Dispose (linha 29). Este método foi criado para implementar a interface IDisposable, tendo por função liberar recursos alocados pela referência associada à propriedade DefaultContext.

Listagem 9. Classe UnitOfWork

  01 using System;
  02 using System.Data.Entity;
  03 using TesteCodeFirst.Utils.ORM;
  04
  05 namespace TesteCodeFirst.DAL.Base
  06 {
  07     public class UnitOfWork : IUnitOfWork
  08     {
  09         private DbContext _defaultContext;
  10
  11         public DbContext DefaultContext
  12         {
  13             get
  14             {
  15                 return _defaultContext;
  16             }
  17         }
  18
  19         public UnitOfWork()
  20         {
  21             this._defaultContext = new DefaultContext();
  22         }
  23
  24         public void Commit()
  25         {
  26             DefaultContext.SaveChanges();
  27         }
  28
  29         public void Dispose()
  30         {
  31             DefaultContext.Dispose();
  32         }
  33     }
  34 }

A definição do tipo OcupacaoDAO está na Listagem 10. Essa estrutura deriva da classe abstrata BaseRepository<T> (criada através do projeto TesteCodeFirst.Utils.ORM), sendo que para o placeholder “<T>” foi informado como parâmetro a entidade Ocupacao (de forma que instâncias de OcupacaoDAO sejam utilizadas exclusivamente na manipulação de objetos baseados neste tipo).

Além dos métodos públicos herdados de BaseRepository<T>, a classe OcupacaoDAO conta ainda com o método ListarOcupacoes (linha 11). Esta operação será utilizada para retornar informações sobre ocupações profissionais possíveis dentro de uma consultoria de TI, funcionando da seguinte maneira:

· Inicialmente é invocado o método GetQueryable (definido BaseRepository<T>), a fim de obter um objeto que permita a execução de instruções que serão convertidas para comandos SQL;

· Na sequência a operação OrderBy (disponibilizada pela extensão LINQ) é acionada, com o intuito de ordenar os dados de ocupações de acordo com as descrições destas;

· Por fim, o método ToList é acionado, gerando uma coleção de objetos do tipo Ocupacao a ser devolvida como resultado da execução de ListarOcupacoes.

Listagem 10. Classe OcupacaoDAO

  01 using System;
  02 using System.Collections.Generic;
  03 using System.Linq;
  04 using TesteCodeFirst.Entities;
  05 using TesteCodeFirst.Utils.ORM;
  06
  07 namespace TesteCodeFirst.DAL
  08 {
  09     public class OcupacaoDAO : BaseRepository<Ocupacao>
  10     {
  11         public List<Ocupacao> ListarOcupacoes()
  12         {
  13             List<Ocupacao> ocupacoes =
  14                 this.GetQueryable().OrderBy(
  15                     o => o.NomeOcupacao).ToList();
  16             return ocupacoes;
  17         }
  18     }
  19 }

A classe ProfissionalDAO (Listagem 11) corresponde à última estrutura a ser implementada dentro do projeto TesteCodeFirst.DAL. Este tipo será utilizado na manipulação de informações envolvendo o cadastro de profissionais de tecnologia dentro da aplicação de exemplo.

Além dos métodos herdados da classe BaseRepository, foram definidos ainda em ProfissionalDAO os seguintes métodos:

· ListarResumoProfissionais (linha 11): esta operação possibilita a consulta a informações de consultores cadastrados no banco de testes;

· CPFJaExistente (linha 28): este método tem por finalidade verificar se um CPF já foi cadastrado anteriormente na base de dados (recebendo como parâmetros o Id do registro manipulado naquele instante, além do CPF).

Conforme é possível observar, o retorno da operação ListarResumoProfissionais será uma coleção de instâncias do tipo ResumoDadosProfissional. Isto acontecerá a partir de chamadas aos métodos GetQueryable (declarado em BaseRepository), OrderBy, Select (estes dois últimos são parte integrante da extensão LINQ) e, finalmente, ToList (esta última ação irá gerar uma lista genérica contendo referências baseadas na classe ResumoDadosProfissional).

Quanto ao método CPFJaExistente, é possível observar o uso das operações GetQueryable e Where (da linha 30 em dia) para a obtenção de uma coleção em que constem dados de um CPF já em uso por outros registros. A partir disto, o método Count (linha 34) é acionado, com o retorno desta ação indicando se o CPF em questão já foi utilizado no cadastro de outro profissional.

Listagem 11. Classe ProfissionalDAO

  01 using System;
  02 using System.Collections.Generic;
  03 using System.Linq;
  04 using TesteCodeFirst.Entities;
  05 using TesteCodeFirst.Utils.ORM;
  06
  07 namespace TesteCodeFirst.DAL
  08 {
  09     public class ProfissionalDAO : BaseRepository<Profissional>
  10     {
  11         public List<ResumoDadosProfissional> ListarResumoProfissionais()
  12         {
  13             List<ResumoDadosProfissional> profissionais =
  14                 this.GetQueryable()
  15                     .OrderBy(p => p.NmProfissional)
  16                     .Select(p => new ResumoDadosProfissional()
  17                            {
  18                                Id = p.Id.Value,
  19                                CPF = p.CPF,
  20                                NmProfissional = p.NmProfissional,
  21                                DsOcupacao = p.Ocupacao.NomeOcupacao,
  22                                VlHora = p.VlHora.Value
  23                            })
  24                     .ToList();
  25             return profissionais;
  26         }
  27
  28         public bool CPFJaExistente(int id, string cpf)
  29         {
  30             var profissionaisEncontrados =
  31                 this.GetQueryable()
  32                     .Where(p => p.Id != id &&
  33                                 p.CPF == cpf);
  34             return (profissionaisEncontrados.Count() > 0);
  35         }
  36     }
  37 }

Nota: O projeto TesteCodeFirst.DAL também irá utilizar o Entity Framework. Tal referência poderá ser adicionada por meio do utilitário NuGet.

Implementando os objetos de negócio

Na Class Library TesteCodeFirst.BLL encontram-se classes que enviam chamadas à camada de acesso a dados (projeto TesteCodeFirst.DAL). Nessas estruturas também foram declaradas as regras de negócio da aplicação. Estão definidos em TesteCodeFirst.BLL os tipos OcupacaoBO e ProfissionalBO.

Na Listagem 12 está a implementação do tipo OcupacaoBO. Analisando a estrutura desta classe, é possível observar a existência da operação ListarOcupacoes (linha 14). Acionando um método de mesmo nome a partir de uma instância da classe OcupacaoDAO vinculada à propriedade “DAO” (linha 16), esta operação devolverá como resultado uma coleção de objetos do tipo Ocupacao.

Quanto à propriedade “DAO” (linha 12), nota-se que a mesma foi marcada com o atributo DependencyAttribute. Conforme já descrito anteriormente, esta indicação fará com que o mecanismo de injeção do Unity Application Block associe em tempo de execução uma instância do tipo OcupacaoDAO a esta propriedade.

Listagem 12. Classe OcupacaoBO

  01 using System;
  02 using System.Collections.Generic;
  03 using Microsoft.Practices.Unity;
  04 using TesteCodeFirst.Entities;
  05 using TesteCodeFirst.DAL;
  06
  07 namespace TesteCodeFirst.BLL
  08 {
  09     public class OcupacaoBO
  10     {
  11         [Dependency]
  12         public OcupacaoDAO DAO { get; set; }
  13
  14         public List<Ocupacao> ListarOcupacoes()
  15         {
  16             return DAO.ListarOcupacoes();
  17         }
  18     }
  19 }

Nota: Por fazer uso do Unit Application Block, uma referência para este pacote deverá ser adicionada ao projeto TesteCodeFirst.BLL via utilitário NuGet.

Já na Listagem 13 é apresentado o código que implementa a classe ProfissionalBO.

Uma propriedade baseada no tipo ProfissionalDAO foi declarada na linha 12, sendo resolvida por meio de técnicas de injeção de dependências a partir Unit Application Block (já que foi também marcada com o atributo DependencyAttribute). Importante destacar que tal referência servirá de base para que os diferentes métodos de ProfissionalDAO (descritos a seguir) invoquem a camada de acesso a dados.

O método ListarResumoProfissionais (linha 14) ao ser invocado acionará uma operação de mesmo nome na classe ProfissionalDAO, retornando como resultado de sua execução uma coleção de objetos do tipo ResumoDadosProfissional.

A operação ObterDadosProfissional (linha 19) recebe como parâmetro o Id de um profissional, acionando a camada de acesso a dados a fim de obter os dados cadastrais do mesmo. Se o registro correspondente for encontrado, será retornada uma instância do tipo Profissional; do contrário, uma exceção será gerada informando que o Id em questão é inválido.

O retorno do método ValidarDadosProfissional (linha 31) será um valor do tipo bool, o qual indica se os dados associados a uma instância da classe Profissional são válidos ou não; se este não for o caso, os parâmetros CampoInconsistente e DescricaoInconsistencia serão preenchidos com informações que identificam o campo e o problema detectado.

A operação ValidarDadosProfissional verifica primeiramente se o CPF associado a uma instância do tipo Profissional é realmente válido, com isto acontecendo por meio do método ValidarCPF (linha 39).

Além disso, é efetuada outra checagem (linha 46) via método CPFJaExistente (o qual pertence à classe ProfissionalDAO), com o objetivo de determinar se o CPF em questão já se encontra cadastrado na base (partindo da premissa que apenas novos CPFs poderão ser informados).

As operações IncluirDadosProfissional (linha 59), AtualizarDadosProfissional (linha 66) e ExcluirProfissional (linha 73) serão responsáveis pela persistência dos dados modificados a partir da aplicação de testes. A confirmação das mudanças realizadas acontecerá acessando o objeto UnitOfWork da propriedade e invocando na sequência o método Commit (como indicado nas linhas 63, 70 e 78).

Contando com um comportamento bastante similar, as operações IncluirDadosProfissional e AtualizarDadosProfissional fazem uso da instância vinculada à propriedade DAO, invocando através desta última os métodos Insert e Update para a inclusão e atualização de registros, respectivamente.

Já a operação ExcluirProfissional será utilizada na remoção de dados de um profissional da base, acionando para isto os métodos FindByPrimaryKey (a fim de obter uma instância do tipo Profissional) e Delete por meio da instância do tipo ProfissionalDAO.

Listagem 13. Classe ProfissionalBO

  01 using System;
  02 using System.Collections.Generic;
  03 using Microsoft.Practices.Unity;
  04 using TesteCodeFirst.Entities;
  05 using TesteCodeFirst.DAL;
  06
  07 namespace TesteCodeFirst.BLL
  08 {
  09     public class ProfissionalBO
  10     {
  11         [Dependency]
  12         public ProfissionalDAO DAO { get; set; }
  13
  14         public List<ResumoDadosProfissional> ListarResumoProfissionais()
  15         {
  16             return DAO.ListarResumoProfissionais();
  17         }
  18
  19         public Profissional ObterDadosProfissional(int id)
  20         {
  21             Profissional profissional =
  22                 DAO.FindByPrimaryKey(id);
  23             if (profissional == null)
  24             {
  25                 throw new Exception(
  26                     "Foi informado um Id de Profissional inválido.");
  27             }
  28             return profissional;
  29         }
  30
  31         public bool ValidarDadosProfissional(
  32             Profissional profissional,
  33             out string CampoInconsistente,
  34             out string DescricaoInconsistencia)
  35         {
  36             CampoInconsistente = null;
  37             DescricaoInconsistencia = null;
  38
  39             if (!ValidarCPF(profissional.CPF))
  40             {
  41                 CampoInconsistente = "CPF";
  42                 DescricaoInconsistencia = "CPF inválido.";
  43                 return false;
  44             }
  45
  46             if (DAO.CPFJaExistente(profissional.Id.HasValue ?
  47                      profissional.Id.Value : 0,
  48                      profissional.CPF))
  49             {
  50                 CampoInconsistente = "CPF";
  51                 DescricaoInconsistencia =
  52                     "CPF já cadastrado anteriormente.";
  53                 return false;
  54             }
  55
  56             return true;
  57         }
  58
  59         public void IncluirDadosProfissional(
  60             Profissional profissional)
  61         {
  62             DAO.Insert(profissional);
  63             DAO.UnitOfWork.Commit();
  64         }
  65
  66         public void AtualizarDadosProfissional(
  67             Profissional profissional)
  68         {
  69             DAO.Update(profissional);
  70             DAO.UnitOfWork.Commit();
  71         }
  72
  73         public void ExcluirProfissional(int id)
  74         {
  75             Profissional profissional =
  76                 DAO.FindByPrimaryKey(id);
  77             DAO.Delete(profissional);
  78             DAO.UnitOfWork.Commit();
  79         }
  80
  81         private bool ValidarCPF(string vrCPF)
  82         {
  83             ...
  84         }
  85     }
  86 }

Nota: Para efeitos de simplificação, a implementação do método ValidarCPF foi omitida deste artigo. No link para download do material desta edição será possível obter o código-fonte completo para a solução de exemplo aqui apresentada.

Implementando a aplicação MVC

O último passo para a implementação do exemplo proposto por este artigo será a criação do projeto TesteCodeFirst.MVC. Esta aplicação será do tipo “ASP.NET Web Application”, fazendo uso do framework ASP.NET MVC 5.1.

Serão definidas no projeto TesteCodeFirst.MVC as seguintes estruturas:

· A classe MVCxUnityDependencyResolver, utilizada para ativar o mecanismo de injeção de dependências do Unity Application Block em conjunto com o framework ASP.NET MVC 5.1;

· Um primeiro Controller (HomeController) responsável pela exibição da tela inicial da aplicação, além de outras duas páginas em que constam os objetivos do sistema, além de informações para contato;

· Um segundo Controller em que serão definidas as funcionalidades do cadastro de profissionais de tecnologia (ProfissionaisController);

· Views associadas às diferentes Actions dos Controllers já citados.

Deverão ser adicionadas a esta aplicação MVC (via NuGet):

· Referências que apontem para os assemblies do Entity Framework, Unity Application Block e Unity bootstrapper for ASP.NET MVC (conforme é possível observar na Figura 3);

· As bibliotecas de scripts autoNumeric (para máscaras em campos monetários) e jQuery.MaskedInput (usada para máscaras de CPF, Telefone, dentre outros tipos de dados com uma formatação especial). Tais plugins fazem uso de recursos de jQuery/JavaScript.

Configurando as
dependências do projeto TesteCodeFirst.MVC
Figura 3. Configurando as dependências do projeto TesteCodeFirst.MVC

O projeto TesteCodeFirst.MVC também irá referenciar as Class Libraries implementadas nas seções anteriores.

Nota: O código referente às Views da aplicação de testes também foi omitido, já que não constam nestas estruturas instruções complexas que justifiquem uma discussão mais aprofundada. Os arquivos correspondentes a essas Views podem ser obtidos através do download da solução aqui implementada, bastando acessar no site da revista o link contendo o material deste artigo.

Este mesmo procedimento também foi adotado com a classe HomeController.

O próximo passo será alterar o arquivo Web.config (Listagem 14) da aplicação TesteCodeFirst.MVC, especificando no mesmo:

· Uma referência ao Unity Application Block dentro do elemento “configSections” (linha 4), a fim de permitir do mecanismo de injeção de dependências disponibilizado por esta biblioteca;

· A string de conexão no elemento connectionStrings” (linha 12), a qual apontará para a base de testes utilizada pela aplicação MVC e possuindo o mesmo nome da classe de contexto empregada no acesso a este repositório (“DefaultContext”);

· A cultura utilizada no Brasil, a fim de evitar problemas com formatação e conversão de datas e valores monetários. Forma preenchidos para isto os atributos “culture” e “uiCulture” do elemento “globalization” com o valor “pt-BR” (linha 23);

· Uma seção iniciada pelos elementos unity e container (linhas 28 e 29), em que estão indicadas as classes e interfaces a serem resolvidas pelo Unity Application Block em tempo de execução (através do uso do elemento “register”, além dos atributos “type” e “mapTo”).

Importante destacar no mapeamento da interface IUnitOfWork a existência de uma declaração em que se faz uso do elemento “register” (linha 36); o tipo especificado dentro desta construção (PerRequestLifetimeManager, pertencente ao namespace Microsoft.Practices.Unity da extensão “Unity bootstrapper for ASP.NET MVC”) fará com o Unity Application Block gere uma única referência de classe que implementa IUnitOfWork durante a validade de uma requisição HTTP.

Listagem 14. Arquivo Web.config do projeto TesteCodeFirst.MVC

  01 <?xml version="1.0" encoding="utf-8"?>
  02 <configuration>
  03   <configSections>
  04     <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
  05
  06     ...
  07   </configSections>
  08
  09   ...
  10
  11   <connectionStrings>
  12     <add name="DefaultContext"
  13          connectionString="..."
  14          providerName="System.Data.SqlClient" />
  15   </connectionStrings>
  16
  17   ...
  18
  19   <system.web>
  20
  21     ...
  22
  23     <globalization culture="pt-BR" uiCulture="pt-BR"/>
  24   </system.web>
  25
  26   ...
  27
  28   <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  29     <container>
  30       <register type="TesteCodeFirst.DAL.OcupacaoDAO, TesteCodeFirst.DAL" />
  31       <register type="TesteCodeFirst.DAL.ProfissionalDAO, TesteCodeFirst.DAL" />
  32       <register type="TesteCodeFirst.BLL.OcupacaoBO, TesteCodeFirst.BLL" />
  33       <register type="TesteCodeFirst.BLL.ProfissionalBO, TesteCodeFirst.BLL" />
  34       <register type="TesteCodeFirst.Utils.ORM.IUnitOfWork, TesteCodeFirst.Utils.ORM"
  35                 mapTo="TesteCodeFirst.DAL.Base.UnitOfWork, TesteCodeFirst.DAL">
  36         <lifetime type="Microsoft.Practices.Unity.PerRequestLifetimeManager, Microsoft.Practices.Unity.Mvc" />
  37       </register>
  38     </container>
  39   </unity>
  40
  41   ...
  42
  43 </configuration>

A implementação da classe MVCxUnityDependencyResolver é demonstrada na Listagem 15. Este tipo implementa a interface IDependencyResolver (namespace System.Web.Mvc), sendo esta última utilizada pelo ASP.NET MVC para integrar uma aplicação a um framework de injeção de dependências. Analisando a forma como este tipo foi estruturado, é possível observar:

· Um construtor (linha 12) que espera como parâmetro uma instância do tipo IUnityContainer (namespace Microsoft.Practices.Unity), com este objeto sendo utilizado posteriormente para resolver dependências durante a execução de uma aplicação baseada no ASP.NET MVC (a partir da instância armazenada na referência “_container”);

· A operação GetService (linha 18) será utilizada pelo framework MVC para resolver uma dependência relacionada a um determino tipo. Esta operação irá retornar o valor “null” caso uma classe ou interface não esteja registrada (cabendo ao desenvolvedor gerar a instância correspondente nos pontos em que a mesma for necessária); caso o tipo em questão tenha sido registrado (o que é verificado na linha 20, ao invocar o método IsRegistered por meio da instância do tipo IUnityContainer), o método Resolve será acionado através da referência “_container”;

· O método GetServices (linha 30), o qual permite resolver múltiplas dependências para um mesmo tipo, contando com um comportamento bastante similar àquele apresentado pela operação GetService.

Listagem 15. Classe MVCxUnityDependencyResolver

  01 using System;
  02 using System.Collections.Generic;
  03 using System.Web.Mvc;
  04 using Microsoft.Practices.Unity;
  05
  06 namespace TesteCodeFirst.MVC
  07 {
  08     public class MVCxUnityDependencyResolver : IDependencyResolver
  09     {
  10         private IUnityContainer _container;
  11
  12         public MVCxUnityDependencyResolver(
  13             IUnityContainer container)
  14         {
  15             this._container = container;
  16         }
  17
  18         public object GetService(Type serviceType)
  19         {
  20             if (!_container.IsRegistered(serviceType))
  21             {
  22                 if (serviceType.IsAbstract || serviceType.IsInterface)
  23                 {
  24                     return null;
  25                 }
  26             }
  27             return _container.Resolve(serviceType);
  28         }
  29
  30         public IEnumerable<object> GetServices(Type serviceType)
  31         {
  32             return _container.ResolveAll(serviceType);
  33         }
  34     }
  35 }

Modificações também serão necessárias no arquivo Global.asax (Listagem 16), a fim de tornar possível o funcionamento do Unity Application Block em conjunto com a aplicação TesteCodeFirst.MVC:

· Uma nova instância da classe UnityContainer (namespace Microsoft.Practices.Unity) deverá ser gerada (linha 20). Partindo desta referência, acionar então o método LoadConfiguration (linha 21) com o intuito de carregar as configurações com mapeamentos de tipos que foram declaradas no arquivo Web.config. A operação LoadConfiguration definida dentro do namespace Microsoft.Practices.Unity.Configuration;

· Também será necessário gerar uma instância da classe MVCxUnityDependencyResolver (com o construtor recebendo como parâmetro a referência do tipo UnityContainer), para posterior registro deste objeto através de uma chamada ao método estático SetResolver (linha 22).

Esta última operação é parte integrante da classe DependencyResolver (esta última estrutura encontra-se localizada namespace System.Web.Mvc).

Listagem 16. Ajustes a serem realizados no arquivo Global.asax

  01 using System;
  02 using System.Collections.Generic;
  03 using System.Linq;
  04 using System.Web;
  05 using System.Web.Mvc;
  06 using System.Web.Optimization;
  07 using System.Web.Routing;
  08 using Microsoft.Practices.Unity;
  09 using Microsoft.Practices.Unity.Configuration;
  10
  11 namespace TesteCodeFirst.MVC
  12 {
  13     public class MvcApplication : System.Web.HttpApplication
  14     {
  15         protected void Application_Start()
  16         {
  17
  18             ...
  19
  20             UnityContainer container = new UnityContainer();
  21             container.LoadConfiguration();
  22             DependencyResolver.SetResolver(
  23                 new MVCxUnityDependencyResolver(container));
  24         }
  25     }
  26 }

A estrutura do Controller ProfissionaisController é apresentada na Listagem 17. Os métodos declarados neste tipo correspondem às diferentes Actions utilizadas no cadastro de profissionais. Estas operações foram marcadas com os atributos HttpGetAttribute e HttpPostAttribute, os quais determinam o tipo de requisição HTTP processada por uma Action (GET ou POST, respectivamente).

O uso do atributo HttpGetAttribute é opcional (quando nenhum atributo estiver vinculado a uma Action, o framework MVC assumirá que a mesma deverá tratar requisições do tipo GET).

As Actions responsáveis operações inclusão, alteração e/ou exclusão são implementadas por meio de dois métodos de mesmo nome. O primeiro equivale a uma requisição do tipo GET, tendo por função gerar a View para a montagem da página em que serão digitadas/exibidas informações.

O segundo é do tipo POST, recebendo como parâmetro uma instância da classe que estará sendo manipulada (o tipo Profissional, no caso específico desta aplicação de exemplo).

Importante destacar ainda que a classe ProfissionaisController também fará uso do mecanismo de injeção de dependências do Unity Application Block. Para isto, as propriedades Profissional_BO (linha 15) e Ocupacao_BO (linha 18) foram marcadas com o atributo DependencyAttribute. Estas referências serão utilizadas nas diferentes Actions que fazem parte do Controller ProfissionaisController, conforme será descrito mais adiante.

Listagem 17. Estrutura do Controller ProfissionaisController

  01 using System;
  02 using System.Collections.Generic;
  03 using System.Linq;
  04 using System.Web;
  05 using System.Web.Mvc;
  06 using Microsoft.Practices.Unity;
  07 using TesteCodeFirst.BLL;
  08 using TesteCodeFirst.Entities;
  09
  10 namespace TesteCodeFirst.MVC.Controllers
  11 {
  12     public class ProfissionaisController : Controller
  13     {
  14         [Dependency]
  15         public ProfissionalBO Profissional_BO { get; set; }
  16
  17         [Dependency]
  18         public OcupacaoBO Ocupacao_BO { get; set; }
  19
  20         [HttpGet]
  21         public ActionResult Index()
  22         {
  23             ...
  24         }
  25
  26         private ActionResult ProcessarGravacaoDadosProfissional(
  27             Profissional profissional, bool inclusao)
  28         {
  29             ...
  30         }
  31
  32         [HttpGet]
  33         public ActionResult Inserir()
  34         {
  35             ...
  36         }
  37
  38         [HttpPost]
  39         public ActionResult Inserir(Profissional profissional)
  40         {
  41             ...
  42         }
  43
  44         [HttpGet]
  45         public ActionResult Editar(int id)
  46         {
  47             ...
  48         }
  48
  50         [HttpPost]
  51         public ActionResult Editar(Profissional profissional)
  52         {
  53             ...
  54         }
  55
  56         [HttpGet]
  57         public ActionResult Excluir(int id)
  58         {
  59             ...
  60         }
  61
  62         [HttpPost]
  63         public ActionResult Excluir(Profissional profissional)
  64         {
  65             ...
  66         }
  67     }
  68 }

A Action Index (Listagem 18) exibirá os consultores já cadastrados. Através de uma chamada ao método ListarResumoProfissionais (linha 4) no objeto associado à propriedade Profissional_BO, será retornada uma coleção de objetos do tipo ResumoDadosProfissional; tais referências serão então repassadas ao método View, gerando assim o resultado da Action Index no formato HTML.

Listagem 18. Action Index (Controller ProfissionaisController)

  01 [HttpGet]
  02 public ActionResult Index()
  03 {
  04     return View(Profissional_BO.ListarResumoProfissionais());
  05 }

Para a Action Inserir (Listagem 19), responsável pela inclusão de um novo profissional na base de dados, é possível observar que foram definidas três operações.

Um primeiro método chamado ProcessarGravacaoDadosProfissional (linha 1) tratará solicitações do tipo POST, recebendo como parâmetro uma instância do tipo Profissional com as informações relativas a um consultor, além de um flag que indica se acontecerá a inclusão ou alteração de um registro no banco de exemplo.

Esta operação faz uso do método ValidarDadosProfissional definido na classe ProfissionalBO (linha 8); caso existam inconsistências nos dados a serem gravados na base, o método AddError é acionado a partir propriedade ModelState (linha 12), com o objetivo de gerar uma mensagem que descreva o problema que deverá ser corrigido.

Outras inconsistências geradas a partir dos atributos vinculados às propriedades da classe Profissional também poderão ser detectadas. Independente de onde tenham sido geradas mensagens descrevendo falhas durante uma tentativa de cadastro ou atualização, a propriedade IsValid (pertencente ao objeto ModelState) assumirá o valor “false”.

Em tais situações o objeto ViewBag (BOX 3) será utilizado, associando-se ao mesmo dados relativos às ocupações disponíveis: estas informações servirão de base para a exibição das opções disponíveis em um controle do tipo DropDown. O método View é então acionado (linha 19), de forma que não prossiga com a gravação daquele registro.

Se nenhum problema for diagnosticado com as informações preenchidas por um usuário, a propriedade Profissional_BO é utilizada novamente. Para inclusões, será acionada a operação IncluirDadosProfissional (linha 23); já na alteração de um registro invoca-se o método AtualizarDadosProfissional (linha 25).

O método Inserir (linha 30) cuidará de requisições do tipo GET, preenchendo para isto a propriedade ViewBag com informações de ocupações possíveis (linha 40), além de uma instância sem informações da classe Profissional (linha 41). A finalidade destas ações é gerar um formulário para cadastro de profissionais, utilizando para isto o método View (linha 41).

Já a segunda versão do método Inserir (linha 45) recebe como parâmetro uma instância da classe Profissional, acionando o método ProcessarGravacaoDadosProfissional (linha 47) para proceder com a inclusão das informações na base de dados.

Listagem 19. Método ProcessarGravacaoDadosProfissional e Action Inserir (Controller ProfissionaisController)

  01 private ActionResult ProcessarGravacaoDadosProfissional(
  02     Profissional profissional, bool inclusao)
  03 {
  04     string CampoInconsistente = null;
  05     string DescricaoInconsistencia = null;
  06
  07     if (ModelState.IsValid && !Profissional_BO
  08             .ValidarDadosProfissional(profissional,
  09                 out CampoInconsistente,
  10                 out DescricaoInconsistencia))
  11     {
  12         ModelState.AddModelError(CampoInconsistente,
  13             DescricaoInconsistencia);
  14     }
  15
  16     if (!ModelState.IsValid)
  17     {
  18         ViewBag.Ocupacoes = Ocupacao_BO.ListarOcupacoes();
  19         return View(profissional);
  20     }
  21
  22     if (inclusao)
  23         Profissional_BO.IncluirDadosProfissional(profissional);
  24     else
  25         Profissional_BO.AtualizarDadosProfissional(profissional);
  26     return RedirectToAction("Index");
  27 }
  28
  29 [HttpGet]
  30 public ActionResult Inserir()
  31 {
  32     var ocupacoes = Ocupacao_BO
  33         .ListarOcupacoes().ToList();
  34     ocupacoes.Insert(0,
  35         new Ocupacao()
  36         {
  37             Id = null,
  38             NomeOcupacao = "Selecione uma Ocupação..."
  39         });
  40     ViewBag.Ocupacoes = ocupacoes;
  41     return View(new Profissional());
  42 }
  43
  44 [HttpPost]
  45 public ActionResult Inserir(Profissional profissional)
  46 {
  47     return ProcessarGravacaoDadosProfissional(
  48         profissional, true);
  49 }

BOX 3. A propriedade ViewBag

ViewBag é uma propriedade definida no tipo básico ControllerBase e que faz uso do recurso conhecido como Dynamics. Graças a esta característica é possível definir atributos a um objeto para utilização posterior, descartando com isto a necessidade de declarar um novo tipo para cumprir tal objetivo.

Na Listagem 20 está a implementação da Action Editar, a qual será formada por dois métodos:

· O primeiro dos métodos (linha 2) está associado a requisições do tipo GET. Esta operação faz uso das instâncias vinculadas às propriedades Profissional_BO e Ocupacao_BO, invocando os métodos ObterDadosProfissional (linha 5) e ListarOcupacoes (linha 6) a fim de retornar as informações para a alteração em tela do cadastro de um profissional contratado pela consultoria;

· A segunda versão do método Editar (linha 11) aciona a operação ProcessarGravacaoDadosProfissional, sendo que a partir disto acontecerá a atualização dos dados relativos a um consultor (desde que não existam inconsistências nas informações preenchidas por um usuário).

Listagem 20. Action Editar (Controller ProfissionaisController)

  01 [HttpGet]
  02 public ActionResult Editar(int id)
  03 {
  04     Profissional profissional =
  05         Profissional_BO.ObterDadosProfissional(id);
  06     ViewBag.Ocupacoes = Ocupacao_BO.ListarOcupacoes();
  07     return View(profissional);
  08 }
  09
  10 [HttpPost]
  11 public ActionResult Editar(Profissional profissional)
  12 {
  13     return ProcessarGravacaoDadosProfissional(
  14         profissional, false);
  15 }

Por fim, a Action Excluir (Listagem 21) estará encarregada da remoção do cadastro de um consultor da base de dados. Uma primeira implementação do método Excluir (linha 2) será utilizada para a exibição de informações sobre um profissional, solicitando ao usuário se o mesmo deseja realmente excluir o registro correspondente.

Já a segunda versão da operação Excluir (linha 10) removerá da base de dados um determinado consultor. Em ambos os métodos que compõem a Action Excluir foram utilizadas operações definidas na classe ProfissionalBO.

Listagem 21. Action Excluir (Controller ProfissionaisController)

  01 [HttpGet]
  02 public ActionResult Excluir(int id)
  03 {
  04     Profissional profissional =
  05         Profissional_BO.ObterDadosProfissional(id);
  06     return View(profissional);
  07 }
  08
  09 [HttpPost]
  10 public ActionResult Excluir(Profissional profissional)
  11 {
  12     Profissional_BO.ExcluirProfissional(
  13         profissional.Id.Value);
  14     return RedirectToAction("Index");
  15 }

Teste da aplicação MVC criada

Na Figura 4 é apresentada a tela inicial da aplicação TesteCodeFirst.MVC.

Tela inicial da
aplicação TesteCodeFirst.MVC
Figura 4. Tela inicial da aplicação TesteCodeFirst.MVC

Ao clicar no link para acesso ao cadastro (Figura 5), serão listados todos os consultores já registrados na base de dados, bem como disponibilizadas opções para a inclusão de um novo profissional, a alteração ou ainda a exclusão de dados pré-existentes.

Consulta a
Profissionais de TI
Figura 5. Consulta a Profissionais de TI

Quando for acionada a funcionalidade para inclusão de um novo profissional, será exibida ao usuário uma tela similar à apresentada na Figura 6. Já a Figura 7 corresponde ao procedimento de editar os dados de um consultor já cadastrado anteriormente.

Inclusão de um novo
Profissional
Figura 6. Inclusão de um novo Profissional

Alteração de dados de
um Profissional
Figura 7. Alteração de dados de um Profissional

Finalmente, ao solicitar a exclusão do cadastro de um profissional, será mostrada ao usuário uma tela similar à da Figura 8.

Exclusão de um
Profissional
Figura 8. Exclusão de um Profissional

Frameworks ORM representam uma importante ferramenta para um desenvolvimento mais produtivo em aplicações que dependam de bancos de dados relacionais. Encapsulando rotinas para a execução de operações de consulta, inclusão, alteração e exclusão de informações a partir do uso de estruturas típicas da Orientação a Objetos, estes mecanismos procuram descomplicar o trabalho de codificação de funcionalidades CRUD.

Os fornecedores deste tipo de recurso procuram incorporar padrões e práticas de uso consagrado em seus frameworks, buscando com isto fornecer um meio mais simplificado e seguro para a interação com bases relacionais.

O Entity Framework corresponde à solução ORM da Microsoft para a construção de aplicações voltadas à plataforma .NET. esta ferramenta permite desde a geração de um modelo com as classes que representam entidades de negócio a partir de uma ferramenta gráfica, passando ainda pela utilização de tipos com uma estrutura simples e totalmente baseados em código .NET.

Esta última abordagem é conhecida como Code First e foi demonstrada ao longo deste artigo, possuindo como grande vantagem a possibilidade de reuso de classes já definidas anteriormente. Qualquer que seja o modo de trabalho escolhido (Model First, Database First, Code First), todas estas alternativas suportam os mesmos recursos, fato esse que contribui para que o Entity Framework se adapte mais facilmente a qualquer tipo de projeto.

Conforme apresentado na solução de exemplo, o Entity Framework Code First pode inclusive ser utilizado na criação de um mecanismo genérico que suporte operações de CRUD. Combinado recursos como Generics e funcionalidades que permitem inferir a estrutura de uma base a partir de informações de metadata (como relacionamentos entre diferentes entidades), o modo Code First mostra com isto uma grande flexibilidade.

Assim, é possível concluir que esta abordagem é capaz de atender, sem maiores percalços, às demandas de desenvolvedores preocupados com a obtenção de um código mais enxuto e que dispense o uso de arquivos .edmx.

Relacionados