VAGAS DE EMPREGO

balta.io balta.io
  • Cursos
  • Carreiras
  • Para sua Empresa
  • Livros
    • Background Services
    • Blazor com .NET 8
    • Segurança em APIs
    • Futuro do C# 12
    • Nullable Types
    • Clean Code
  • Blog

Seja Premium
balta.io

  • Cursos
  • Carreiras
  • Para sua Empresa
  • Agenda
  • Livros
    • Background Services
    • Blazor com .NET 8
    • Segurança em APIs
    • Futuro do C# 12
    • Nullable Types
    • Clean Code
  • Blog
  • Player
Seja Premium

Entre ou Cadastre-se

  • Home
  • Artigos
  • Entity Framework e Value Objects


👉 Temos uma versão mais atualizada deste artigo no nosso novo Blog

Entity Framework e Value Objects

Neste artigo vamos aprender a mapear objetos de valor no Entity Framework Core, de maneira simples e fácil.

O que são objetos de valor?

Objetos de valor ou Value Objects são estruturas imutáveis que compoe entidades de negócio, concentrando regras e removendo a necessidade de reescrita de código.

Primitive Obsession

Uma das premissas para o uso dos Value Objects é evitar a obsessão primitiva ou Primitive Obsession, um termo utilizado quando trabalhamos apenas com tipos primitivos nas nossas entidades.

Para ilustrar melhor, vamos tomar como base a classe User, que basicamente qualquer sistema tem.

public class User 
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
}

Este é um caso clássico de entidade anêmica. Ela basicamente reflete os dados da tabela e não o negócio em sí. Para piorar ela ainda está totalmente obsecada por tipos primitivos (int, string, bool e por ai vai).

Agora imagina que você precisa validar este E-mail no usuário, depois precisa novamente do E-mail no Aluno, depois no Fornecedor, depois na Venda, e assim por diante.

Como você deve imaginar, o E-mail por exemplo, tem sua regra de negócio, e a mesma precisará se aplicar para todo e qualquer E-mail no sistema.

Desta forma, por que não criar um tipo chamado E-mail, que contém as validações.

public class Email 
{
    public Email(string address)
    {
        if (string.IsNullOrEmpty(address) || address.Length < 5)
            throw new InvalidEmailException();

        Address = address.ToLower().Trim();
        const string pattern = @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$";

        if (!Regex.IsMatch(address, pattern))
            throw new InvalidEmailException();
    }

    public string Address { get; }
}

Com nosso tipo criado, passamos a utilizar mais tipos complexos e menos tipos primitivos, e como o E-mail compoe uma entidade mas não tem uma razão para existir sozinho, ele é classificado como um Value Object.

public class User 
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Email Email { get; set; }
    public string Password { get; set; }
}

Imutabilidade

Uma das coisas que sempre trago para as minhas entidades e objetos de valores é a imutabilidade, ou seja, uma vez construído, o objeto não é mais alterado (Pelo menos não externamente).

Isto nos leva a privar ou remover o set das propriedades por exemplo, como fizemos acima no Email. Este processo também permite que a classe seja passível de extensões mas fechada para modificações.

Confira mais sobre DDD, modelagem de domínios, entidades e objetos de valor no nosso curso Modelando Domínios Ricos

Comparação

Apenas para intuito de comparação, vamos acompanhar a mudança de uma entidade anêmica para a segmentação em objetos de valores.

Como vimos o primeiro passo é sair de uma classe anêmica, obssecada por tipos primitivos, para algo mais estruturado, voltado a objetos de valores.

// BAD 🚫
public class User 
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
}
// GOOD ✅
public class User 
{
    public User(Name name, Email email, Password password)
    {
        Name = name;
        Email = email;
        Password = password;
    }

    public Name Name { get; }
    public Email Email { get; }
    public Password Password { get; }
}

IMPORTANTE O código acima não está completo, é apenas um exemplo, não tome ele como base para seus projetos.

Concluímos que a medida que andamos com objetos de valor, começamos a mover as regras para dentro deles e ter menos regras nas entidades em sí.

Isto ocorre em todo sistema, onde teremos cada vez menos código, até chegar nos Controllers da API por exemplo, que terão uma ou duas linhas de código.

Este movimento de "dentro para fora" é importante e necessário, afinal, quanto mais próximo do núcleo, mais livre de infraestrutura e mais fácil de testar é seu código.

Se quiser conferir mais sobre testes de unidade, confira nosso curso Refatorando para testes de unidade.

Enfim, para se ter uma noção desse movimento de regras de negócio, este é um objeto de valor para Email que temos aqui no balta.

public class Email : ValueObject
{
    // Obrigatório para funcionar com EF
    protected Email()
    {
    }

    public Email(string address)
    {
        if (string.IsNullOrEmpty(address) || address.Length < 5)
            throw new InvalidEmailException();

        Address = address.ToLower().Trim();
        const string pattern = @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$";

        if (!Regex.IsMatch(address, pattern))
            throw new InvalidEmailException();

        Verified = false;
        VerificationCode = GenerateVerificationCode();
        VerificationCodeExpireDate = DateTime.UtcNow.AddHours(2);
    }

    public string Address { get; } = string.Empty;
    public bool Verified { get; private set; }
    public string VerificationCode { get; private set; } = string.Empty;
    public DateTime VerificationCodeExpireDate { get; private set; } = DateTime.UtcNow.AddHours(2);

    public void Verify(string verificationCode)
    {
        if (verificationCode != VerificationCode)
            throw new ArgumentException("Código de ativação inválido");

        if (VerificationCodeExpireDate > DateTime.UtcNow)
            throw new ArgumentException("Código de ativação expirado");

        Verified = true;
    }

    public void GenerateNewVerificationCode()
    {
        Verified = false;
        VerificationCode = GenerateVerificationCode();
        VerificationCodeExpireDate = DateTime.UtcNow.AddHours(8);
    }

    public void Expire() => Verified = false;

    private static string GenerateVerificationCode() => Guid.NewGuid().ToString().ToUpper()[..8];

    public static implicit operator string(Email email) => email.Address;

    public static implicit operator Email(string address) => new(address);

    public override string ToString() => Address;
}

Mapeamento

Depois desta longa introdução sobre o que são e para que servem os objetos de valor, vem a pergunta... como colocar esta informação no banco de dados?

Uma das premissas da modelagem de domínios neste formato é não se prender ao banco. Lembra que no começo deste artigo comentamos sobre entidades anêmicas e como elas são apenas uma visualização de uma tabela do banco?

Porém, uma hora precisamos persistir estes valores em algum lugar, e normalmente fazemos isto utilizando algum ORM como por exemplo o Entity Framework.

Value Objects não tem Id

Diferente das entidades, objetos de valor não possuem um identificador. Isto se dá ao fato deles serem parte da composição de entidades e não haver motivo para os mesmos existirem sozinhos.

Mapeando o usuário

Vamos tomar como base para o mapeamento inicial, ele faz herança de uma classe base chamada Entity que possui a definição do Id e tem apenas um campo que não é um VO, o Active.

public class User : Entity
{
    // Obrigatório para o EF funcionar
    protected User() { }

    public User(Name name, Email email, Password password)
    {
        Name = name;
        Email = email;
        Password = password;
        Active = false;
    }

    public Name Name { get; }
    public Email Email { get; }
    public Password Password { get; }
    public bool Active { get; }
}

public class UserMap : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder)
    {
        builder.ToTable("User");

        builder.HasKey(x => x.Id);

        builder.Property(x => x.Active)
            .HasColumnName("Active")
            .IsRequired(true);
    }
}

Como podemos notar, aqui temos o uso do mapeamento do Entity Framework sob o usuário, porém ignoramos as propriedades que são objetos de valor.

Caso queira aprender mais sobre Entity Framework e mapeamento, confere nosso curso Fundamentos do Entity Framework.

Mapeando objetos de valor

O Entity Framework não possui um método para mapear objetos de valor explicitamente, e nem precisa disso. Como comentamos anteriormente, os objetos de valor não tem Id, eles funcionam apenas como uma composição das entidades.

Desta forma, podemos por exemplo utilizar o OwnsOne (Possui um...) para dizer que o usuário possui um objeto aninhado que será mapeado para a mesma tabela.

builder.OwnsOne(x => x.Email)
    .Property(x => x.Address)
    .HasColumnName("Email")
    .IsRequired(true);

builder.OwnsOne(x => x.Email)
    .Property(x => x.Verified)
    .HasColumnName("EmailVerified")
    .IsRequired(true);

builder.OwnsOne(x => x.Email)
    .Property(x => x.VerificationCode)
    .HasColumnName("EmailVerificationCode")
    .IsRequired(true);

builder.OwnsOne(x => x.Email)
    .Property(x => x.VerificationCodeExpireDate)
    .HasColumnName("EmailVerificationCodeExpireDate")
    .IsRequired(true);

Também fazemos uso do HasColumnName para nomear a coluna a ser gerada na tabela de uma forma legível para nós posteriormente.

Isto é tudo o que precisamos para mapear objetos de valor com Entity Framework. Neste momento você deve conseguir ler, salvar, editar e excluir informações utilizando seus VOs.

public class UserMap : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder)
    {
        builder.ToTable("User");

        builder.HasKey(x => x.Id);

        builder.Property(x => x.Active)
            .HasColumnName("Active")
            .IsRequired(true);

        builder.OwnsOne(x => x.Email)
            .Property(x => x.Address)
            .HasColumnName("Email")
            .IsRequired(true);

        builder.OwnsOne(x => x.Email)
            .Property(x => x.Verified)
            .HasColumnName("EmailVerified")
            .IsRequired(true);

        builder.OwnsOne(x => x.Email)
            .Property(x => x.VerificationCode)
            .HasColumnName("EmailVerificationCode")
            .IsRequired(true);

        builder.OwnsOne(x => x.Email)
            .Property(x => x.VerificationCodeExpireDate)
            .HasColumnName("EmailVerificationCodeExpireDate")
            .IsRequired(true);
    }
}

Conclusão

Trabalhar com objetos de valor torna o código mais burocrático e um pouco mais complexo, porém nos trás diversas vantagens a longo prazo como reuso do código por exemplo.

Em adicional, persistir estas informações com Entity Framework é uma tarefa simples, basta utilizar o OwnsOne.

Populares

Priority Queue

Priority Queue ou fila prioritária é um tipo de lista inclusa no C# 10 que permite que seus itens...


Implicit Operators no C#

Implicit Operators permitem adicionar comportamentos de conversão a objetos Built In ou complexos...


ASP.NET 5 – Autenticação e Autorização com Bearer e JWT

Este artigo atualmente utiliza a versão 5.0.0-rc.1 do ASP.NET/.NET, o que significa que ainda não...


Clean Code - Guia e Exemplos

Saiba como manter seu código limpo (Clean Code) seguindo algumas práticas sugeridas pelo Robert C...


Git e GitHub - Instalação, Configuração e Primeiros Passos

Git é um sistema de controle de versões distribuídas, enquanto GitHub é uma plataforma que tem o ...


Compartilhe este artigo



Conheça o autor

André Baltieri

André Baltieri

Microsoft MVP

Me dedico ao desenvolvimento de software desde 2003, sendo minha maior especialidade o Desenvolvimento Web. Durante esta jornada pude trabalhar presencialmente aqui no Brasil e Estados Unidos, atender remotamente times da ?ndia, Inglaterra e Holanda, receber 8x Microsoft MVP e realizar diversas consultorias em empresas e projetos de todos os tamanhos.





3.133

Aulas disponíveis

292

horas de conteúdo

76.626

Alunos matriculados

53.182

Certificados emitidos





Comece de graça agora mesmo!

Temos mais de 21 cursos totalmente de graça e todos com certificado de conclusão.

Começar


Prefere algo mais Premium?

Conheça nossos planos



Premium anual

Compra única, parcelada em até
12x no cartão de crédito


12x R$

99

,79

=R$ 1.197,44
  • 1 ano de acesso
  • Acesso à todo conteúdo
  • Emissão de Certificado
  • Tira Dúvidas Online
  • 67 cursos disponíveis
  • 10 carreiras disponíveis
  • 161 temas de tecnologia
  • Conteúdo novo todo mês
  • Encontros Premium

Começar agora

Política de privacidade



Precisa de ajuda?

Dúvidas frequentes



  • Posso começar de graça?

    Sim! Basta criar sua conta gratuita no balta.io e começar seus estudos. Nós contamos com diversos cursos TOTALMENTE gratuitos e com certificado de conclusão.

  • Vou ter que pagar algo?

    Nós temos cursos gratuitos e pagos, porém você não precisa informar nenhum dado de pagamento para começar seus estudos gratuitamente conosco. Os cursos gratuitos são completos e com certificado de conclusão, você não paga nada por eles.

    Porém, caso queira algo mais Premium , você terá acesso à diversos benefícios que vão te ajudar ainda mais em sua carreira.

  • Por onde devo começar?

    Siga SEMPRE as nossas Carreiras , elas vão te orientar em todos os sentidos. Os cursos já estão organizados em categorias e carreiras para facilitar seu aprendizado.
    Nossa sugestão para aprendizado é começar pelo Backend e seguindo para Frontend e Mobile.

    • Backend
    • Frontend
    • Mobile

  • Os cursos ensinam tudo que preciso?

    Nenhum curso no mundo vai te ensinar tudo, desculpa ser sincero! Os cursos são uma base, eles fornecem por volta de 30% do que você precisa aprender, o resto é com você, com dedicação e MUITA prática.

  • O que eu devo estudar?

    Java ou .NET? Angular ou React? Xamarin ou Flutter? A resposta é simples e direta: "Você já sabe o básico?"

    Se você ainda não sabe BEM o básico, ou seja, os fundamentos, OOP, SOLID, Clean Code, está perdendo tempo estudando Frameworks ou até coisas mais avançadas como Docker. Foque nos seus objetivos primeiro.
    Agora se você está indeciso sobre qual Framework estudar, a boa notícia é que o mercado neste momento está bem aquecido e você tem várias oportunidade. Desta forma o que levaríamos em conta para tomar esta decisão seria:

    • Já sei o básico
    • O Framework/Tecnologia tem mercado onde eu estou (região)
    • O Framework/Tecnologia é utilizado em uma empresa onde quero atual
    • O Framework/Tecnologia resolve meu problema
    • Eu gosto de utilizar o Framework/Tecnologia

  • Estou pronto para estudar no balta.io?

    Com certeza! O primeiro passo é começar e você pode fazer isto agora mesmo!

    Começar de graça

Ainda tem dúvidas?





Assine nosso Newsletter

Receba em primeira mão todas as nossas novidades.

Cadastrar


balta.io

Sobre

  • Como funciona?
  • Seja Premium
  • Agenda
  • Blog
  • Todos os cursos

Cursos

  • Frontend
  • Backend
  • Mobile
  • Fullstack

Suporte

  • Termos de uso
  • Privacidade
  • Cancelamento
  • Central de ajuda

Redes Sociais

  • Telegram
  • Facebook
  • Instagram
  • YouTube
  • Twitch
  • LinkedIn
  • Discord