Trabalhando com Tipos Complexos no EF Core

O Entity Framework Core apesar de funcional ainda não está 100%. Algumas funcionalidades requerem workarounds ou simplesmente não existem. Mas temos algo para comemorar, agora podemos trabalhar com tipos complexos sem impactar na modelagem de nossas entidades.

Entity Framework Core

Trabalhando com Tipos Complexos no EF Core

Eu trabalho muito com entidades ricas que seguem as boas práticas de modelagem do DDD. Para possuirmos um domínio rico é necessário dar a maior expressividade possível ao nosso código muitas vezes deixando de usar tipos primitivos para usar tipos complexos.

O Entity Framework Core antes da versão 2 simplesmente não conseguia mapear tipos complexos, ou seja, era necessário impactar diretamente na modelagem de nossas entidades apenas para contornar uma deficiência do ORM. Que coisa horrível não?

Inclusive muitos desenvolvedores simplesmente abriram mão de usar o EF Core e adotaram outras soluções como o Dapper por exemplo. Mas agora esse problema não existe mais!

Mapeando um Tipo Complexo

Vamos supor que sua entidade Cliente possui um campo CPF, muitas vezes basta criar um tipo string e pronto, mas o ideal seria que o CPF fosse um Value Object, pois permite reusabilidade de código além de permitir que a própria classe CPF cuide da questão da validação. Além disso podem existir outros dados como Data de Emissão por exemplo:

Os tipos complexos podem ser Value Objects ou outras entidades, o importante é que o modelo seja o mais rico e expressivo possível.

No EF Core 2 agora é possível utilizar Fluent API de forma nativa (sem gambiarras) e mapear os tipos complexos a partir do recurso OwnsOne:

Depois de todo trabalho de modelagem feito basta executar os comandos:

  • Add-Migration
  • Update-Database

E pronto! Seu modelo está criado.

Mapeamento Gerado no Entity Framework Core

Inserindo e recuperando a entidade através do EF Core

Uma vez que estamos trabalhando com modelos ricos, devemos seguir as boas práticas (utilizar private setters, ad-hoc setters, construtores e validação de consistência):

  • É necessário criar um construtor protected nas classes para que o EF Core possa recuperar do banco e criar a instância, pois ele não entende o construtor.
  • As validações foram feitas na mão e lançando exceptions para fins de demonstração
  • O objeto é recuperado exatamente como se deseja do banco

Entidade recuperada no Entity Framework Core

Testes

Testar é importante certo? Esse projeto foi disponibilizado com o código fonte completo inclusive testando todas as situações previstas:

Testes de Unidade no Entity Framework Core

Com este código fonte eu espero ajudar você a não ter mais nenhum problema de mapeamento de tipos complexos do seu domínio rico com Entity Framework Core.

Resumindo

O projeto completo está no meu GitHub

O EF Core ainda apresenta problemas irritantes como não possuir mapeamento Many to Many. É necessário fazer uma gambiarra horrível (mas funciona).

Não deixe de utilizar modelos ricos em suas aplicações, é muito importante para um bom design de código e uma modelagem correta de seu domínio. Agora o EF Core não será mais um problema.


Caso esteja interessado em conhecer mais sobre Testes, TDD, ASP.NET, DDD, Padrões de Arquitetura e boas práticas de desenvolvimento não deixe de conferir as ementas dos meus cursos:

Vamos continuar a troca de experiências, deixe seu comentário abaixo. Se gostou e concorda com o artigo, compartilhe com seus colegas para transmitirmos o conhecimento para o máximo de pessoas possíveis.

26 pensou em “Trabalhando com Tipos Complexos no EF Core

  1. Show de bola, era o que EF Core precisava para estar quase 100% completo. Obrigado pelo artigo, sempre claro e preciso.

    Abs.

    Dariel

  2. Eduardo, não encontrei na documentação nenhuma alternativa para esses itens abaixo:
    modelBuilder.Conventions.Remove();
    modelBuilder.Conventions.Remove();
    Também há alguma alternativa para definição genérica de tipos no banco de dados?
    Obrigado pelo ótimo conteúdo.

    • Você pode resolver assim:


      protected override void OnModelCreating(ModelBuilder modelBuilder)
      {
      modelBuilder.Entity().ToTable("EntidadeA");
      modelBuilder.Entity().ToTable("EntidadeB");

      //Ou diretamente em cada classe de mapeamento no FluentAPI como consta no exemplo.

      // Para o cascade delete:

      foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
      {
      relationship.DeleteBehavior = DeleteBehavior.Restrict;
      }

      base.OnModelCreating(modelBuilder);
      }

      • Boa noite, na minha classe de mapeamento eu estou utilizando a seguinte forma :

        public override void Map(EntityTypeBuilder builder)
        {
        base.Map(builder);
        builder.HasOne(b => b.Cidade).WithMany(c => c.Bairros).HasForeignKey(b => b.CidadeId).OnDelete(DeleteBehavior.Restrict);
        }

  3. Bom artigo, uma dúvida como é um exemplo eu imagino que no sistema real você usaria um retorno do erro não utilizando o “throw new Exception” e sim Notification ou algo desse tipo certo?

  4. Fala Eduardo, tudo bem?

    Eu estou tentando usar esta feature tem um tempo, mas tenho uma dúvida:
    Via de regra, um VO é imutável. Quando você muda as propriedades de um VO, muda o VO inteiro, porque ele define uma informação como um todo (Sua identidade é a composição de todas as suas propriedades).

    Quando precisamos atualizar um VO, instanciamos um novo VO e substituímos o antigo pelo novo.

    No entanto, o EF core me retorna o erro descrito aqui:

    https://github.com/aspnet/EntityFrameworkCore/issues/10580

    Só o post que ele retorna como relacionado/solução, não me parece ser a resposta real para o problema.

    Consegue pensar em uma solução pra isso?
    Obrigado pela força;

  5. Parabéns pelo artigo Eduardo,

    Gosto muito dos seus artigos pois são sempre diretos e sempre trazem assuntos muito importantes e relevantes.

  6. E aquele problema no select, o AsNoTracking(), que obrigava usar um micro ORM para consultas e evitar um erro do EF, foi solucionado nessa versão também?

  7. Boa tarde Eduardo.

    Nao estou conseguindo gerar no banco as tabelas, poderia me ajudar, nao apresentar nenhum erro qdo executo o comando migrations.

  8. Cara Eduardo, sempre a frente das informações e parabéns pela nomeação Microsoft Regional Director, vc merece.

    Saberia me dizer quando vai se abrir a porta do paraíso e o EF Core vai se juntar ao ORACLE, sabemos que o SQL Server é prioridade, mas com tantas melhorias como o Blazer etc, não é possível que a Microsoft não possa considerar o Oracle em seus planos.
    Teria alguma solução alternativa de Asp.net Core x Oracle

    Abraços

  9. Boa noite ! Parabens pelo material, inspirado nele fiz muita coisa 90% do que eu queria. Porem gostaria de saber como que eu mapeio uma classe dentro de outra.
    vc faz de um VO :
    builder.OwnsOne(c => c.CPF, cpf =>
    {
    cpf.Property(c => c.Numero)
    .IsRequired()
    .HasColumnName(“CPF”)
    .HasColumnType(“varchar(11)”);

    cpf.Property(c => c.DataEmissao)
    .IsRequired()
    .HasColumnName(“CPFDataEmissao”);
    });
    eu queria saber de uma classe dentro da outra
    exemplo

    Classe Auditoria {datahora, usuario}
    Classe Aluno {id, nome, Auditoria}
    Como seria isso ?

Os comentários estão fechados.