Engenharia Reversa no Entity Framework 5

Por Renato Haddad

Novembro, 2012

JJ856239.060DE5057573180CEC6D227C6D3E2207(pt-br,MSDN.10).png

Neste artigo irei abordar o uso da engenharia reversa num dos tópicos que vem crescendo muito para os desenvolvedores, o Entity Framework 5. A evolução deste desde as primeiras versões é notável e atualmente o Entity Framework F 5 é público, open source, ou seja, você pode baixar do www.codeplex.com e melhorar a versão, caso precise.

O que é Engenharia Reversa no Entity Framework 5?

O Entity Framework é uma forma de abstrair uma fonte de dados de forma relacional, não importa qual seja a fonte, pois já há diversos (mais de 32) provedores de drives para o EF, por exemplo Oracle, mysql, etc. Para explicar a resposta, veja a figura 1, onde temos diversos cenários para o EF 5. Caso não tenha um banco de dados e deseja escrever código da aplicação, use os cenários chamado de Novo Database (conforme a figura 1). Destacam-se o Model First – onde você define o modelo EDMX e gera o banco a partir deste – e o Code First – onde você cria as classes e o banco de dados é criado em tempo de execução. Se olharmos o segundo cenário de já termos um banco de dados, temos o Database First e o Code First. Ambos podem gerar um EDMX com as devidas classes automaticamente. No entanto, aqui é que entra a Engenharia Reversa, onde temos a possibilidade de não gerar o EDMX, e sim as classes de forma completamente separadas (graças aos templates T4 do VS.NET que já estão instalados). Isto nos permite trabalhar com as classes e o fantástico contexto (com o DbContext) sem ter um EDMX.

JJ856239.9786684E7A94964F61B811966D1FD1BF(pt-br,MSDN.10).png

Figura 1 – Cenários para o EF 5

Por que usar Engenharia Reversa?

Em todos os tipos de projetos, e principalmente em times médios e grandes é comum ter o papel da pessoa que administra o banco de dados (DBA), onde toda e qualquer alteração deve passar sempre pelo crivo deste. Agora, o DBA, mais do que nunca, é o parceiro do desenvolvedor, pois o DBA pode validar uma nova estrutura (seja uma nova entidade, um novo tipo e tamanho de campo, etc) sem ter que colocar a mão no SQL, mysql, Oracle, etc. Ele apenas irá validar a estrutura na classe escrita em C# ou VB.NET e através do Migrations, o próprio desenvolvedor submeterá tais alterações. Para mais detalhes do Migrations, leia o artigo do Carlos dos Santos em http://msdn.microsoft.com/pt-br/library/jj591330.

Agora, existem casos onde o próprio desenvolvedor faz tudo, desde o banco até a aplicação, então, fica tudo sob o domínio e o conhecimento dele. Particularmente gosto muito da Engenharia Reversa do EF 5 porque nos permite ter mais controle e produtividade no desenvolvimento, as classes e a estrutura do projeto ficam separadas, você consegue extender e customizar as classes.

O que preciso para usar a Engenharia Reversa?

Este tópico não vem instalado no Visual Studio .NET 2012 (também vale para o VS 2010), então, acesse o http://visualstudiogallery.msdn.microsoft.com/ e pesquise sobre “Entity Framework Power Tools”. É gratuito, basta fazer o download e instalar. Caso esteja com o VS aberto, é preciso reiniciá-lo.

Agora vamos criar um projeto, abra o VS 2012 e selecione New Project. Para mostrar tal funcionalidade, nada melhor que um projeto em C# do tipo Console Application. Dê o nome do projeto de EngReversaEF5, conforme a figura 2. Clique em OK para criar o projeto.

JJ856239.497F3B03FE5DDE8ABFF8893CE2A601D8(pt-br,MSDN.10).png

Figura 2 – Novo projeto

A primeira coisa a fazer é abrir o Solution Explorer, clicar com o botão direito no nome do projeto e selecionar Entity Framework / Reverse Engineer Code First, conforme a figura 3. Dica: caso a opção Entity Framework não aparecer é porque você não instalou o pacote citado anteriormente.

JJ856239.2DA27B8C307C5202DC013DD00186F6A5(pt-br,MSDN.10).png

Figura 3 – Opção para engenharia reversa.

Será aberta uma janela solicitando qual é o banco de dados. No meu caso o servidor é EARTH e o banco é o famoso Northwind, conforme a figura 4. Obviamente você deverá selecionar o seu servidor e o respectivo banco de dados que queira aplicar a engenharia reversa.

JJ856239.E517C02846C2CFAFEC888F07CDE50581(pt-br,MSDN.10).png

Figura 4 – Banco de dados no SQL Server 2012

Clique no botão OK e o VS se encarrega de criar e gerar todas as classes e o mapeamento, assim como o Contexto. Assim que finalizar, não se assuste com os erros porque neste projeto nós ainda não referenciamos o Entity framework 5. Eu fiz isto obrigatoriamente para você saber que é preciso referenciar o EF 5. Existem alguns tipos de projetos com ASP.NET MVC 4 que já faz a referência automaticamente, mas como este é de Console, faremos na mão.

Para isto, selecione o menu Tools / Library Package Manager / Manage NuGet Packages for Solution, pesquise sobre “entity framework 5”, conforme a figura 5. Assim que mostrar o pacote chamado EntityFramework, clique em Install e pronto, só alegria! Veja que a versão é a 5.0.0 e quando você instala qualquer pacote via NuGet, tenha certeza que terá a última versão e não precisa ficar pesquisando na internet onde está, qual versão usar, como instalar, quais referências fazer, etc, tudo isto é o NuGet que faz pra nós.

JJ856239.99D04DB85302D243C240D2D918E5FB9A(pt-br,MSDN.10).png

Figura 5 – Instalação via NuGet

Nos bastidores, abra a pasta References e veja que foram referenciadas todas as classes, sendo a EntityFramework, System.Data.Entity e a System.ComponentModel.DataAnnotations.

Modelagem das Classes

Abra a pasta Models (criada automaticamente com a Engenharia Reversa) e veja que temos todas as classes escritas em C# (.cs) de cada entidade e consultas do banco de dados Northwind.

Por exemplo, abra a classe Customer e veja como que foi gerada. Ela contém no construtor as referências com as quais ela se relaciona e a estrutura das propriedades em si.

using System;
using System.Collections.Generic;

namespace EngReversaEF5.Models
{
    public class Customer
    {
        public Customer()
        {
            this.Orders = new List<Order>();
            this.CustomerDemographics = new List<CustomerDemographic>();
        }

        public string CustomerID { get; set; }
        public string CompanyName { get; set; }
        public string ContactName { get; set; }
        public string ContactTitle { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string Region { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
        public string Phone { get; set; }
        public string Fax { get; set; }
        public virtual ICollection<Order> Orders { get; set; }
        public virtual ICollection<CustomerDemographic> CustomerDemographics { get; set; }
    }
}

Agora vem o mais legal nesta parte de engenharia reversa. Na pasta Models/Mapping, abra o arquivo CustomerMap.cs. Veja que todo o mapeamento das propriedades estão escritos em FluentAPI, onde cada propriedade contém as devidas configurações, por exemplo, quem é a chave primária (HasKey), se é requerido (IsRequired), o tamanho máximo (HasMaxLenght), etc. Mais abaixo neste código você vê o mapeamento da entidade com a classe, ou seja, veja a seção chamada “Table & Column Mappings”, onde há a referência de qual entidade é no banco de dados (ToTable), assim como a propriedade (HasColumnName). Com isto você consegue criar um dicionário de dados, e isto vale para todas as classes geradas.

using System.ComponentModel.DataAnnotations;
using System.Data.Entity.ModelConfiguration;

namespace EngReversaEF5.Models.Mapping
{
    public class CustomerMap : EntityTypeConfiguration<Customer>
    {
        public CustomerMap()
        {
            // Primary Key
            this.HasKey(t => t.CustomerID);

            // Properties
            this.Property(t => t.CustomerID)
                .IsRequired()
                .IsFixedLength()
                .HasMaxLength(5);

            this.Property(t => t.CompanyName)
                .IsRequired()
                .HasMaxLength(40);

            this.Property(t => t.ContactName)
                .HasMaxLength(30);

            this.Property(t => t.ContactTitle)
                .HasMaxLength(30);

            this.Property(t => t.Address)
                .HasMaxLength(60);

            this.Property(t => t.City)
                .HasMaxLength(15);

            this.Property(t => t.Region)
                .HasMaxLength(15);

            this.Property(t => t.PostalCode)
                .HasMaxLength(10);

            this.Property(t => t.Country)
                .HasMaxLength(15);

            this.Property(t => t.Phone)
                .HasMaxLength(24);

            this.Property(t => t.Fax)
                .HasMaxLength(24);

            // Table & Column Mappings
            this.ToTable("Customers");
            this.Property(t => t.CustomerID).HasColumnName("CustomerID");
            this.Property(t => t.CompanyName).HasColumnName("CompanyName");
            this.Property(t => t.ContactName).HasColumnName("ContactName");
            this.Property(t => t.ContactTitle).HasColumnName("ContactTitle");
            this.Property(t => t.Address).HasColumnName("Address");
            this.Property(t => t.City).HasColumnName("City");
            this.Property(t => t.Region).HasColumnName("Region");
            this.Property(t => t.PostalCode).HasColumnName("PostalCode");
            this.Property(t => t.Country).HasColumnName("Country");
            this.Property(t => t.Phone).HasColumnName("Phone");
            this.Property(t => t.Fax).HasColumnName("Fax");
        }
    }
}

Até aqui já temos as classes e o mapeamento, mas onde é que está o contexto da classe? Na pasta Models localize e abra o arquivo NorthwindContext.cs. Logo na declaração da classe, veja que ela herda de DbContext, o qual é o responsável pelo gerenciamento do contexto, pelo rastreamento, pelo relacionamento das classes, ou seja, vale a pena selecionar o DbContext e pressionar F12 para ver a definição em detalhes.

No construtor da classe é feita a referência à chave que está no arquivo App.Config o qual define a conexão com o banco de dados, assim como o provider para o SQL Server, neste caso. Ou seja, se mudar qualquer detalhe da conexão, basta alterar o App.Config (se fosse um projeto de web seria Web.Config).

<connectionStrings>
    <add name="NorthwindContext" connectionString="Data Source=EARTH;Initial Catalog=Northwind;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
  </connectionStrings>

Em seguida são definidas as propriedades de cada entidade (agora classe .cs) referenciando o tipo DbSet, o qual é reponsável pelo CRUD, pesquisa, execução de instruções, etc de cada entidade. Na listagem a seguir eu omiti algumas classes devido ao tamanho do artigo, mas fique tranquilo que todas estarão listadas de acordo com o banco de dados.

using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using EngReversaEF5.Models.Mapping;

namespace EngReversaEF5.Models
{
    public class NorthwindContext : DbContext
    {
        static NorthwindContext()
        {
            Database.SetInitializer<NorthwindContext>(null);
        }

public NorthwindContext()
: base("Name=NorthwindContext")
{
}

        public DbSet<Category> Categories { get; set; }
        public DbSet<CustomerDemographic> CustomerDemographics { get; set; }
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Employee> Employees { get; set; }
        public DbSet<Product> Products { get; set; }
        public DbSet<Region> Regions { get; set; }
        public DbSet<Shipper> Shippers { get; set; }
        public DbSet<Invoice> Invoices { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new CategoryMap());
            modelBuilder.Configurations.Add(new CustomerDemographicMap());
            modelBuilder.Configurations.Add(new CustomerMap());
            modelBuilder.Configurations.Add(new EmployeeMap());
            modelBuilder.Configurations.Add(new ProductMap());
            modelBuilder.Configurations.Add(new RegionMap());
            modelBuilder.Configurations.Add(new ShipperMap());
            modelBuilder.Configurations.Add(new InvoiceMap());
        }
    }
}

Compilação do projeto

Chegou a tão esperada hora de compilar o projeto. O que sempre esperamos é Build com sucesso, nenhum erro, nem warning. Mas, neste caso, é preciso fazer alguns ajustes. Então, selecione o menu Build / Build Solution ou pressione F6. A lista de erros aparecerá informando (neste nosso caso do banco Northwind) que algumas classes (e já adianto que são referências as consultas que se tornaram classes) é preciso adicionar uma referência. Então, como exemplo, abra a classe RegionMap e note que o erro está no “DatabaseGeneratedOption”. O jeito mais rápido e fácil de corrigir isto é colocar o cursor sobre o “DatabaseGeneratedOption”, dar um CTRL + . (ponto) + ENTER para adicionar a referência a seguir na lista de using.

using System.ComponentModel.DataAnnotations.Schema;

Consultas dados

Como já temos toda a estrutura de códigos pronta, vamos criar um código para listar todos os produtos. Abra o arquivo Program.cs e digite a seguinte listagem:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using EngReversaEF5.Models;

namespace EngReversaEF5
{
    class Program
    {
        static void Main(string[] args)
        {
            using (NorthwindContext ctx = new NorthwindContext())
            {
                var produtos = ctx.Products.Where(p => p.UnitPrice <= 20)
                                           .OrderBy(p => p.ProductName);
                foreach (var item in produtos)
                {
                    Console.WriteLine(string.Format("{0} - {1:c2} - estoque: {2:n0}",
                        item.ProductName, item.UnitPrice, item.UnitsInStock));
                }

                Console.ReadLine();
            }
        }
    }
}

Veja que há a referência para “using EngReversaEF5.Models;” para se ter acesso ao contexto (NorthwindContext), seguido de uma expressão lambda para selecionar todos os produtos onde o preço seja menor ou igual a 20. Como se não bastasse, quero que os produtos sejam ordenardos (.OrderBy) pelo nome do produto (ProductName). Em seguida, é montado um looping foreach para listar algumas propriedades. Pressione F5 para executar o projeto e veja o resultado na Figura 6.

JJ856239.C19C91D7C6E0E0EDE4D8A3F495A6D589(pt-br,MSDN.10).png

Figura 6 – Produtos listados

Em http://bit.ly/Nq798N você encontra mais detalhes sobre o Entity Framework 5, uso de engenharia reversa, uso do Migrations passo a passo. Faça um bom uso da engenharia reversa nos projetos, não deixe de experimentar as boas práticas do FluentAPI.

Mostrar: