Há cenários de mapeamento em que não podem ser alcançados usando Annotationse e por isso o FluentAPI é considerado um recurso mais avançado que o Data Annotations. As diferenças estão nas abordagens, uma vez que a decoração por atributos (Data Annotations) atua sobre classes que representam Models
e suas propriedades, enquanto o FluentAPI atua em um nível global, enunciando as regras diretamente no contexto da aplicação. **
Ao invés de colocar atributos (Data Annotations) nas classes... Você configura tudo dentro do OnModelCreating
com código fluente.
Fluent API não é algo exclusivo do Entity Framework, é um estilo de programação. No EF Core, virou sinônimo de Configurar como as entidades do sistema se comportam no banco de dados... mas via código fluente, não por atributos:
Se tiver conflito:
Podemos considerar utilizar o Fluent em cenários mais específicos, onde requer um controle mais fino sobre o mapeamento entre as entidades e o banco de dados. Geralmente é mais vantajoso em aplicações de grande porte ou de alta complexidade, onde as necessidades específicas de configuração vão além do que as Data Anotations podem oferecer.
O código do Fluent API fica sempre do método OnModelCreating(ModelBuilder modelbuilder)
, na classe DBContext
.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Restaurante>(entity =>
{
entity.HasKey(r => r.Id); // Primary Key
entity.Property(r => r.Nome)
.IsRequired() // NOT NULL
.HasMaxLength(100); // VARCHAR(100)
entity.Property(r => r.Faturamento)
.HasColumnType("decimal(10,2)"); // Tipo no banco
});
}
É uma boa prática na aplicação modularizar, ou seja colocar as configurações de cada Entidade em arquivos próprios. Dessa forma, você mantém o método OnModelCreating
limpo, legível e com responsabilidade única: apenas aplicar essas configurações.
SeuProjeto/
│
├── Domain/
│ └── Models/
│ ├── Restaurante.cs
│ └── Endereco.cs
│
├── Infra/
│ ├── Context/
│ │ └── EscolaContext.cs
│ └── **Configurations/**
│ **├── RestauranteConfiguration.cs**
│ **└── EnderecoConfiguration.cs**
│
└── Program.cs
Uma observação interresante é que em sistemas maiores, a modularização pode ser levada ainda mais longe. Pode ser organizada em configurações por áreas funcionais ou módulos, agrupando entidades relacionadas em namespaces ou pastas específicas, por exemplo:
└── Data
└── Configuration
├── Contas
│ └── ContaConfiguration.cs
│
├── Clientes
│ └── ClienteConfiguration.cs
│
└── Pedidos
├── PedidoConfiguration.cs
└── ItemPedidoConfiguration.cs
Na pasta RestauranteConfiguration
public class RestauranteConfiguration : IEntityTypeConfiguration<Restaurante>
{
public void Configure(EntityTypeBuilder<Restaurante> entity)
{
entity.ToTable("Restaurantes");
entity.HasKey(r => r.Id);
entity.Property(r => r.Nome).IsRequired().HasMaxLength(100);
entity.Property(r => r.Faturamento).HasColumnType("decimal(10,2)");
}
}
No DbContext
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new RestauranteConfiguration());
}
Nosso objetivo é fazer um projeto rápido de um “CRUD”, mas iremos focar apenas na configuração via Fluent API. Nesse projeto vamos focar na organização de pastas padrão de mercado e boas práticas.
/ProjetoRestaurante
│
├── Domain
│ ├── Entities
│ │ └── Restaurante.cs
│
├── Infra
│ ├── Data
│ │ ├── Context
│ │ │ └── AppDbContext.cs
│ │ └── Mappings
│ │ └── RestauranteMapping.cs
│
├── Program.cs
├── appsettings.json
└── ProjetoRestaurante.csproj
namespace ProjetoRestaurante.Domain.Entities;
public class Restaurante
{
public int Id { get; set; }
public string Nome { get; set; }
public string CNPJ { get; set; }
public string Telefone { get; set; }
}
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ProjetoRestaurante.Domain.Entities;
namespace ProjetoRestaurante.Infra.Data.Mappings;
public class RestauranteMapping : IEntityTypeConfiguration<Restaurante>
{
public void Configure(EntityTypeBuilder<Restaurante> builder)
{
builder.ToTable("Restaurantes");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.HasColumnName("Id")
.IsRequired();
builder.Property(x => x.Nome)
.HasColumnName("Nome")
.IsRequired()
.HasMaxLength(100);
builder.Property(x => x.CNPJ)
.HasColumnName("CNPJ")
.IsRequired()
.HasMaxLength(14);
builder.Property(x => x.Telefone)
.HasColumnName("Telefone")
.HasMaxLength(20);
}
}
Esse cara tá dizendo assim:
"Ei EF, a configuração de como a entidade Restaurante deve ser mapeada pro banco tá lá na classe RestauranteMapping. Usa ela!"
using Microsoft.EntityFrameworkCore;
using ProjetoRestaurante.Domain.Entities;
using ProjetoRestaurante.Infra.Data.Mappings;
namespace ProjetoRestaurante.Infra.Data.Context;
public class AppDbContext : DbContext
{
public DbSet<Restaurante> Restaurantes { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new RestauranteMapping());
base.OnModelCreating(modelBuilder);
}
}