DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

Data Annotation em .NET

Conceito

Ao trabalhar com Code First, existem convenções que são feitas no EF. Mas, as vezes você pode desejar não seguir as convenções e fazer algo diferente. As duas maneiras são usando Annotations e FluentAPI.

Alguns exemplos de convenções são:

  1. Chave Primária: Propriedades chamadas ID ou <NomeDaClasse>Id **são automaticamente configuradas como chaves primárias
  2. Colunas: O EF Core cria colunas para todas as propriedades de uma classe de entidade com o mesmo nome da propriedade por padrão.

O que é Data Annotation

Usamos Data Annotation para validação de dados dos nossos Models, além disso, definem regras de requisitos de bancos de dados e outras configurações de mapeamento diretamente no código do modelo. Ao colocar os decoradores antes do atributo desejado, é adicionado um comportamento que permite o .NET runtime executar esses processamentos adicionais que possibilitam a validação dos campos, propriedades e classes.

public class Order
{
    [Key]
    public int OrderID { get; set; }

    [Required(ErrorMessage = "CustomerName is required")]
    [StringLength(100, ErrorMessage = "CustomerName cannot be longer than 100 characters")]
    public string CustomerName { get; set; }

    [RegularExpression(@"^\d+.?\d{0,2}$", ErrorMessage = "Invalid total")]
    [Range(1, 500, ErrorMessage = "Total must be between 1 and 500")]
    public decimal Total { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
  • A annotation [Key] é colocada na propriedade OrderID, que marca como identificador único
  • A annotation [Required] e [StringLength] colocada na propriedade CustomerName garante que o campo não fique vazio e que não ultrapasse 100 caracteres.
  • A annotation [RegualarExpression] e [Range] no campo Total força uma validação específica de números

Vantagens e Desvantagens

Vantagens

  1. Simplicidade: Fáceis de entender e aplicar, tornam o código mais legível e intuitivo, principalmente para quem está começando
  2. Integração com ASP NET MVC: Por ser altamente integradas com o ASP NET MVC, elas são automaticamente utilizadas nas validações do lado do servidor e cliente em formulários
  3. Menos código: Por serem declarativas, podem reduzir a quantidade de código boilerplate necessário para definir regras de validação

Desvantagens

  1. Limitação em Complexidade: Em cenários complexos onde é preciso maior controle como as entidades são mapeadas para o bancos de dados, podem ser insuficientes. Por exemplo, relacionamento complexos entre entidades, chaves compostas ou configurações específicas de colunas.
  2. Mistura de Preocupações e Responsabilidades: Pode misturar as preocupações e responsabilidades de mapeamento de banco de dados com a lógica do negócio, que leva um acoplamento indesejado entre essas camadas.
  3. Dificuldade em Manter e Escalar: Em projetos grandes, onde as entidades possuem muitas proriedades e relações complexas, usar Data Annotation pode levar a um modelo (model) difícil de manter. Todas as regras de validação e mapeamento são embutidas no modelo, o que pode dificultar a manutenção e evolução do código.

Atributos

Os atributos servem para definir regras de validação, requisitos colunas de banco de dados (como tamanho de campo obrigatoriedade) e configurações de mapeamentos diretamente nas propriedades da classe de modelo.

Existem diversos atributos que pode ser adicionado em sua aplicação. Quanto mais você utilizar o Data Annotation, mais naturalmente você será capaz de conhecer e saber quais os tipos de atributos existentes. Vale ressaltar que você pode combinar múltiplos atributos em uma propriedade.

Atributos — Data Annotations vs. Fluent API

Data Annotations anotações são diretamente aplicadas às propriedades do modelo e são refletidas na estrutura da tabela do banco de dados quando as migrações são aplicadas. Elas são simples e diretas, mas podem se tornar difíceis de gerenciar em modelos complexos.

Fluent API oferece mais flexibilidade e centralização das configurações de mapeamento em um único local. É mais adequado para cenários complexos e para projetos em que você precisa de uma configuração mais refinada, como relacionamentos entre entidades ou configurações específicas do banco de dados.

Agora, para exemplificar vamos comparar dois exemplos para o mapeamento de um modelo Telefone usando as duas abordagens e como elas serão refletidas no banco de dados:

1. Mapeamento usando Data Annotations

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

public class Telefone
{
    [Key]
    public int Id { get; set; }

    [Required]
    [StringLength(3, ErrorMessage = "O DDD deve conter 3 caracteres.")]
    public string DDD { get; set; }

    [Required]
    [MaxLength(9, ErrorMessage = "O número do telefone deve ter no máximo 9 caracteres.")]
    public string Numero { get; set; }

    public int TipoTelefoneID { get; set; }

    public int RestauranteID { get; set; }

    public TelefoneTipo TelefoneTipo { get; set; }
    public Restaurante Restaurante { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Reflexo no Banco de Dados SQL:

Id: INT (chave primária)
DDD: VARCHAR(3), obrigatório
Numero: VARCHAR(9), obrigatório
TipoTelefoneID: INT.
RestauranteID: INT.
Enter fullscreen mode Exit fullscreen mode

2. Mapeamento usando Fluent API

Modelo:

public class Telefone
{
    public int Id { get; set; }

    public string DDD { get; set; }

    public string Numero { get; set; }

    public int TipoTelefoneID { get; set; }

    public int RestauranteID { get; set; }

    public TelefoneTipo TelefoneTipo { get; set; }
    public Restaurante Restaurante { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Contexto com Fluent API:

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public DbSet<Telefone> Telefones { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Telefone>()
            .HasKey(t => t.Id);

        modelBuilder.Entity<Telefone>()
            .Property(t => t.DDD)
            .IsRequired()
            .HasMaxLength(3)
            .HasColumnName("DDD");

        modelBuilder.Entity<Telefone>()
            .Property(t => t.Numero)
            .IsRequired()
            .HasMaxLength(9)
            .HasColumnName("Numero");

        modelBuilder.Entity<Telefone>()
            .Property(t => t.TipoTelefoneID)
            .HasColumnName("TipoTelefoneID");

        modelBuilder.Entity<Telefone>()
            .Property(t => t.RestauranteID)
            .HasColumnName("RestauranteID");
    }
}
Enter fullscreen mode Exit fullscreen mode

Reflexo no Banco de Dados SQL:

Id: INT (chave primária)
DDD: VARCHAR(3), obrigatório
Numero: VARCHAR(9), obrigatório
TipoTelefoneID: INT.
RestauranteID: INT
Enter fullscreen mode Exit fullscreen mode

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more