Entity Framework - Usando Fluent API para definir relacionamentos


 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;

Selecione a linguagem Visual Basic e o template Windows Forms Application;

Informe o nome EF_Fluent_API e clique no botão OK;

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;

A seguir localize o Entity Framework, e, após selecionar o pacote clique no botão Install;

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 classe Cliente. Nesta definição do modelo inferimos que essas classes estão relacionadas.

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

Esse recurso chama-se 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 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.

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

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

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

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 dados Vendas 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):

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.

Pegue o projeto completo aqui:  EF6_FluentAPI.zip (sem as referências)

Não vos esqueçais da hospitalidade, porque por ela alguns, não o sabendo, hospedaram anjos.
Hebreus 13:2

Referências:


José Carlos Macoratti