APIs e Microsserviços

17 jul, 2015

Entity Framework – Usando Fluent API para definir relacionamentos

Publicidade

Neste artigo, eu vou mostrar como usar os recursos da Fluent API para realizar configurações e mapeamentos com o Entity Framework.

Ao trabalhar com a abordagem Code First usando o Entity Framework, o comportamento padrão é mapear suas classes POCO para tabelas usando um conjunto de convenções nativas do EF. Às vezes, no entanto, você não pode ou não quer seguir essas convenções e precisa mapear entidades para algo diferente do que as convenções ditam.

Existem duas maneiras de realizar a configuração e o mapeamento no EF quando você não deseja seguir as convenções padrão: Data Annotations e Fluent API.

  1. Data Annotations – Utiliza atributos para realizar o mapeamento e configuração;
  2. Fluent API – Fornece mais funcionalidades que o Data Annotations.

Na abordagem Code First, a Fluent API é mais acessada sobrescrevendo o método OnModelCreating no seu DbContext.

A Fluent API suporta os seguintes tipos de mapeamentos:

Mapeamento Para o Banco de dados
Model-wide Mapping
  • Define o esquema padrão
  • Define as convenções padrão
Entity Mapping
    • Para única ou múltiplas tabelas e esquema
  • Para Tipo Completo
  • Para hierarquia de herança
Property Mapping
    • Para Coluna, Nome da coluna, tipo de coluna, coluna Nullabe(anulável) ou não NULL coluna, o tamanho das colunas, ordem das colunas
  • Para coluna concorrência
  • Para coluna chave estrangeira
  • Para configurar relacionamentos

Neste artigo eu vou mostrar como definir os relacionamentos usando a Fluent API.

Recursos usados:

Criando o projeto no Visual Studio 2013 Express for windows desktop

Abra o VS 2013 Express for windows desktop e clique em New Project. Depois selecione a linguagem Visual Basic e o template Windows Forms Application, informando, em seguida, o nome EF_Fluent_API e clique no botão OK.

ef_fluent1

A seguir, vamos incluir uma referência ao Entity Framework em nosso projeto.

No menu TOOLS, clique em Nuget Package Manager -> Manage Nuget Package for Solutions. Depois localize o Entity Framework, e, após selecionar o pacote, clique no botão Install.

ef_fluent2

Após concluir a instalação, clique no botão Close.

Como o meu objetivo neste artigo é mostrar o mapeamento usando o Fluent API, não vou usar apenas o projeto Windows Forms e não vou criar camadas para separação das responsabilidades.

No menu PROJECT, clique em Add Class e informe o nome Cliente.cs. A seguir, inclua o código abaixo na classe Cliente:

using System.Collections.Generic;
namespace EF6_FluentAPI
{
    public  class Cliente
    {
        public int ClienteId { get; set; }
        public string Nome { get; set; }
        public string Endereco { get; set; }
        public string Telefone { get; set; }
        public string Cidade { get; set; }

        public virtual ICollection<Pedido> Pedidos { get; set; }
    }
}

Repita o procedimento acima e crie a classe Pedido.cs. A seguir, inclua o código abaixo na classe Pedido:

namespace EF6_FluentAPI
{
    public class Pedido
    {
        public int PedidoId { get; set; }
        public string Item { get; set; }
        public int Quantidade { get; set; }
        public int Preco { get; set; }
        public int? ClienteId { get; set; }

        public virtual Cliente Cliente { get; set; }
    }
}

A classe Cliente define a propriedade do tipo coleção para Pedido.

A classe Pedido define a propriedade Cliented, a qual é a chave estrangeira e a propriedade Cliente (também referida como propriedade de Navegação) da classeCliente. Nesta definição do modelo inferimos que essas classes estão relacionadas.

Definimos assim duas classes do nosso domínio, pois o Entity Framework vai usar essa informação para gerar o mapeamento do objeto relacional, criar o banco de dados e as tabelas para a nossa aplicação.

Esse recurso se chama Code-First (o código primeiro) e indica que partimos das definições de nossas classes de domínio para gerar o banco de dados e as tabelas e o contexto com o mapeamento do objeto relacional.

Vamos agora definir uma string de conexão no arquivo App.Config de forma a definir o banco de dados que iremos usar. Abra o arquivo App.Config e defina o seguinte código no arquivo:

<connectionStrings>
    <add
      name="VendasConnectionString"
      connectionString="Data Source=.;Initial Catalog=Vendas;Integrated Security=SSPI"
      providerName="System.Data.SqlClient"/>
  </connectionStrings>

Agora vamos definir a nossa classe de contexto no projeto. Já no menu PROJECT, clique em Add Class e informe o nome VendasContexto e inclua o código abaixo nesta classe:

using System.Data.Entity;
namespace EF6_FluentAPI
{
    public class VendasContexto : DbContext
    {
        public DbSet<Cliente> Clientes { get; set; }
        public DbSet<Pedido> Pedidos { get; set; }
        public VendasContexto()
            : base("name=VendasConnectionString")
        { }
    }
}

A nossa classe VendasContexto herda de DbContext e define as propriedades Clientes e Pedidos com as quais temos acesso as tabelas do banco de dados.

O construtor da classe define o nome do banco de dados que será criado pelo Entity Framework. Isso é tudo que você precisa para iniciar a persistência e a consulta aos dados. Mas podemos melhorar… E vamos fazer isso usando o método OnModelCreating da classe DbContext.

Este método é chamado quando o modelo para o contexto derivado for inicializado. A implementação padrão deste método não faz nada, mas pode ser substituído em uma classe derivada de tal forma que o modelo pode ser configurado. E vamos usar a Fluent API para realizar a configuração de mapeamento entre as entidades.

Vamos, então, abrir a classe VendasContexto e sobrescrever o método OnModelCreating, usando o código abaixo que faz o mapeamento para as tabelas e define o seu relacionamento:

 protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
            //Mapeamento para a tabela Cliente
            //S1: Chave Primária para a tabela Cliente
            modelBuilder.Entity<Cliente>().HasKey(c => c.ClienteId);
            //S2: A chave Identity Key para ClienteId
            modelBuilder.Entity<Cliente>().Property(c => c.ClienteId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            // tamanho máximo para as propriedades Nome,Endereco,Telefone e Cidade
            modelBuilder.Entity<Cliente>().Property(c => c.Nome).HasMaxLength(80);
            modelBuilder.Entity<Cliente>().Property(c => c.Endereco).HasMaxLength(100);
            modelBuilder.Entity<Cliente>().Property(c => c.Telefone).HasMaxLength(20);
            modelBuilder.Entity<Cliente>().Property(c => c.Cidade).HasMaxLength(50);
            //Mapeamento para a tabela Pedido
            //S1: Chave Primaria para a tabela Pedido
            modelBuilder.Entity<Pedido>().HasKey(p => p.PedidoId);
            //S2: Uma chave identity para o PedidoId
            modelBuilder.Entity<Pedido>().Property(p => p.PedidoId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            //S2: O tamanho máximo para o item
            modelBuilder.Entity<Pedido>().Property(p => p.Item).HasMaxLength(50);
            //S3: A chave estrangeira para a tabela Pedido - ClienteId
            modelBuilder.Entity<Pedido>().HasRequired(c => c.Cliente) 
             .WithMany(p => p.Pedidos).HasForeignKey(p => p.ClienteId);
            // A deleção em cascata a partir de Cliente para Pedidos
            modelBuilder.Entity<Pedido>()
                .HasRequired(c => c.Cliente)
                .WithMany(p => p.Pedidos)
                .HasForeignKey(p => p.ClienteId)
                .WillCascadeOnDelete(true);
                   base.OnModelCreating(modelBuilder);
 }

Vamos entender o que foi feito:

 

  • O método OnModelCreating aceita o objeto DbModelBuilder como parâmetro. Esta classe é usada para mapear as classes CLR para o esquema do banco de dados;
  • A implementação deste método define a chave Primária usando o método HasKey();
  • O método HasDatabaseGenerationOption é usado para definir a chave do tipo identidade;
  • O método HasMaxLenght() é usado para definir o tamanho para as colunas do tipo String;
  • O relacionamento da chave estrangeira é definido entre Cliente e Pedido usando a expressão abaixo:

 

    modelBuilder.Entity<Pedido>().HasRequired(c => c.Cliente.WithMany(p => p.Pedidos).HasForeignKey(p => p.ClienteId);

  • O relacionamento um-para-muitos entre as duas tabelas é definida conforme a expressão abaixo:

modelBuilder.Entity<Order>()
.HasRequired(c => c.Customer)
.WithMany(o => o.Orders)
.HasForeignKey(o => o.CustomerId)
.WillCascadeOnDelete(true);

  • Estamos configurando a deleção em cascata usando o método WillCascadeOnDelete();

  • Para definir que uma propriedade é requerida usando o método IsRequired() e HasRequired(), configura um relacionamento requerido a partir do tipo da entidade;

  • O método WithMany permite indicar que uma propriedade contém um relacionamento do tipo Muitos;

  • O método HasForeignKey é usado para indicar qual propriedade é a chave estrangeira.

Nota: Diferença entre os métodos HasRequired e HasOptional:
HasRequired
Configura uma relação necessária com este tipo de entidade. Instâncias do tipo da entidade não serão capazes de serem salvas no banco de dados, a menos que essa relação seja especificada. A chave estrangeira no banco de dados será não-anulável.

HasOptional
Configura um relacionamento opcional a partir deste tipo de entidade. Instâncias do tipo da entidade serão capaz de serem salvas no banco de dados sem que esta relação tenha sido especificada. A chave estrangeira no banco de dados será anulável.

Testando o mapeamento

Para testar o mapeamento, vamos definir um código no formulário form1.vb incluindo um controle Button – btnProcessar – no formulário e definindo o seguinte código no seu evento Click:

 private void btnProcessar_Click(object sender, EventArgs e)
 {
            using (VendasContexto contexto = new VendasContexto())
            {
                try
                {
                    var cliente1 = new Cliente { Nome = "Macoratti", Endereco = "Rua Peru, 100", Telefone = "4555-6666", Cidade = "Lins" };
                    contexto.Clientes.Add(cliente1);
                    contexto.SaveChanges();
                    MessageBox.Show("Cliente criado com sucesso.");
                }
                catch(Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
  }

Agora vamos executar o projeto e clicar no botão de comando. Após receber a mensagem – Cliente criado com sucesso – podemos verificar se o banco de dadosVendas e as tabelas foram criadas.

Abrindo a janela DataBase Explorer no Visual Studio podemos localizar o banco de dados Vendas, as tabelas  Clientes e Pedidoes criadas, bem como os dados do cliente criado no projeto (estamos vendo dois registros porque eu clique duas vezes no botão):

ef_fluent3

O EF pluraliza o nome das tabelas por isso obtivemos o nome Pedidoes. Como ele usa as regras da língua inglesa ele acrescenta ‘es’ ao nome Pedido. Para evitar isso, podemos remover esse comportamento definindo no método OnModelCreating a seguinte linha de código:

modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

Nota: Você vai precisar do namespace System.Data.Entity.ModelConfiguration.Conventions. Constatamos assim, que o mapeamento realizado usando a Fluent API funcionou corretamente.