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.
- Data Annotations – Utiliza atributos para realizar o mapeamento e configuração;
- 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 |
|
Entity Mapping |
|
Property Mapping |
|
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.
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.
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):
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)