Ainda precisamos do AutoMapper em projetos .NET?
*Este é o terceiro artigo da série sobre bibliotecas amplamente utilizadas no ecossistema .NET que deixaram — ou estão deixando — de ser open source e gratuitas. Como vimos no primeiro artigo dessa série, em breve o AutoMapper deixará de ser gratuito. Diante dessa mudança, muitos times estão repensando sua dependência dessa biblioteca. Mas será que ainda precisamos dela? Confesso que já faz um bom tempo desde a última vez que usei AutoMapper em um projeto. A maioria dos sistemas em que tenho trabalhado são grandes, com alta complexidade e demanda, e nesse tipo de cenário o AutoMapper costuma trazer dois principais entraves: 1) Performance O AutoMapper funciona com base em reflexão, ou seja, opera em tempo de execução. Isso significa que, para acessar um tipo, método, propriedade ou campo, o CLR precisa realizar algumas etapas dinamicamente: Carregamento de Metadados: O CLR carrega os metadados do assembly, do tipo e do membro que está sendo acessado. Esses metadados descrevem a estrutura do código, mas seu acesso é mais custoso do que operações diretamente na memória executável. Verificação de Segurança e Acesso: O CLR verifica as permissões de segurança e a acessibilidade do membro, garantindo que a operação seja permitida. Resolução Dinâmica: O CLR identifica o membro com base no nome (string) e na assinatura (no caso de métodos), o que exige uma busca nos metadados. Invocação Dinâmica: Para métodos, o CLR precisa configurar a pilha de execução e fazer a chamada de forma dinâmica — algo consideravelmente mais custoso do que uma chamada direta. Esse processo todo, apesar de poderoso e flexível, pode impactar significativamente a performance em cenários de alta carga ou com grande volume de operações de mapeamento. 2) Sobrecarga Estrutural À medida que um projeto cresce, as entidades de domínio se tornam cada vez mais complexas, com diversas relações e dependências. Rapidamente, os DTOs deixam de refletir fielmente essas entidades, exigindo um número maior de classes de mapeamento e customização — que também tendem a se tornar mais complexas. O resultado é um aumento considerável de código implícito, o que dificulta a leitura, manutenção e, principalmente, o processo de depuração. Adotando uma abordagem mais leve e eficiente Diante dos impactos de performance e da sobrecarga estrutural, uma alternativa que tenho adotado com bons resultados é abrir mão do AutoMapper e realizar os mapeamentos manualmente com o auxílio de métodos simples dentro da classe DTO. Embora isso exija um pouco mais de código explícito, o ganho em clareza, controle e facilidade de manutenção compensa — especialmente em projetos mais robustos. Vamos a um exemplo prático: Considere as seguintes classes, que representam entidades do domínio de um sistema de pedidos: public class ProdutoEntity { public int Id { get; set; } public string Descricao { get; set; } } public class PedidoEntity { public int Id { get; init; } public DateTime DataPedido { get; init; } public ICollection Itens { get; init; } public decimal ValorTotal { get; init; } } public class ItemPedidoEntity { public int Id { get; init; } public int PedidoId { get; init; } public PedidoEntity Pedido { get; init; } public int ProdutoId { get; init; } public ProdutoEntity Produto { get; init; } public int Quantidade { get; init; } public decimal PrecoUnitario { get; init; } } E os seguintes DTOs: public sealed class PedidoDto { public int Id { get; init; } public DateTime DataPedido { get; init; } public ICollection Itens { get; init; } public decimal ValorTotal { get; init; } } public sealed class ItemPedidoDto { public int Id { get; init; } public required string DescricaoProduto { get; init; } public int Quantidade { get; init; } public decimal PrecoUnitario { get; init; } public decimal Subtotal => Quantidade * PrecoUnitario; } Vamos adicionar dois métodos à classe ItemPedidoDto: /// /// Converte uma coleção de entidades de itens de pedido em uma coleção de DTOs. /// public static ICollection MapFromItemPedidoEntityCollection( ICollection itensPedido ) { var resultado = new List(); foreach (var itemPedidoEntity in itensPedido) { resultado.Add( new ItemPedidoDto { Id = itemPedidoEntity.Id, DescricaoProduto = itemPedidoEntity.Produto.Descricao, Quantidade = itemPedidoEntity.Quantidade, PrecoUnitario = itemPedidoEntity.PrecoUnitario } ); } return resultado; } /// /// Converte uma coleção de DTOs de itens de pedido em uma coleção de entidades. /// public static ICollection MapToItemPedidoEntityCollection( ICollection itensPedido ) { var resultado = new List(); foreach (var itemPedidoDto in itensPedido) { resultado.Add(new ItemPedidoEntity {

*Este é o terceiro artigo da série sobre bibliotecas amplamente utilizadas no ecossistema .NET que deixaram — ou estão deixando — de ser open source e gratuitas.
Como vimos no primeiro artigo dessa série, em breve o AutoMapper deixará de ser gratuito. Diante dessa mudança, muitos times estão repensando sua dependência dessa biblioteca. Mas será que ainda precisamos dela?
Confesso que já faz um bom tempo desde a última vez que usei AutoMapper em um projeto. A maioria dos sistemas em que tenho trabalhado são grandes, com alta complexidade e demanda, e nesse tipo de cenário o AutoMapper costuma trazer dois principais entraves:
1) Performance
O AutoMapper funciona com base em reflexão, ou seja, opera em tempo de execução. Isso significa que, para acessar um tipo, método, propriedade ou campo, o CLR precisa realizar algumas etapas dinamicamente:
Carregamento de Metadados: O CLR carrega os metadados do assembly, do tipo e do membro que está sendo acessado. Esses metadados descrevem a estrutura do código, mas seu acesso é mais custoso do que operações diretamente na memória executável.
Verificação de Segurança e Acesso: O CLR verifica as permissões de segurança e a acessibilidade do membro, garantindo que a operação seja permitida.
Resolução Dinâmica: O CLR identifica o membro com base no nome (string) e na assinatura (no caso de métodos), o que exige uma busca nos metadados.
Invocação Dinâmica: Para métodos, o CLR precisa configurar a pilha de execução e fazer a chamada de forma dinâmica — algo consideravelmente mais custoso do que uma chamada direta.
Esse processo todo, apesar de poderoso e flexível, pode impactar significativamente a performance em cenários de alta carga ou com grande volume de operações de mapeamento.
2) Sobrecarga Estrutural
À medida que um projeto cresce, as entidades de domínio se tornam cada vez mais complexas, com diversas relações e dependências. Rapidamente, os DTOs deixam de refletir fielmente essas entidades, exigindo um número maior de classes de mapeamento e customização — que também tendem a se tornar mais complexas.
O resultado é um aumento considerável de código implícito, o que dificulta a leitura, manutenção e, principalmente, o processo de depuração.
Adotando uma abordagem mais leve e eficiente
Diante dos impactos de performance e da sobrecarga estrutural, uma alternativa que tenho adotado com bons resultados é abrir mão do AutoMapper e realizar os mapeamentos manualmente com o auxílio de métodos simples dentro da classe DTO. Embora isso exija um pouco mais de código explícito, o ganho em clareza, controle e facilidade de manutenção compensa — especialmente em projetos mais robustos.
Vamos a um exemplo prático:
Considere as seguintes classes, que representam entidades do domínio de um sistema de pedidos:
public class ProdutoEntity
{
public int Id { get; set; }
public string Descricao { get; set; }
}
public class PedidoEntity
{
public int Id { get; init; }
public DateTime DataPedido { get; init; }
public ICollection<ItemPedidoEntity> Itens { get; init; }
public decimal ValorTotal { get; init; }
}
public class ItemPedidoEntity
{
public int Id { get; init; }
public int PedidoId { get; init; }
public PedidoEntity Pedido { get; init; }
public int ProdutoId { get; init; }
public ProdutoEntity Produto { get; init; }
public int Quantidade { get; init; }
public decimal PrecoUnitario { get; init; }
}
E os seguintes DTOs:
public sealed class PedidoDto
{
public int Id { get; init; }
public DateTime DataPedido { get; init; }
public ICollection<ItemPedidoDto> Itens { get; init; }
public decimal ValorTotal { get; init; }
}
public sealed class ItemPedidoDto
{
public int Id { get; init; }
public required string DescricaoProduto { get; init; }
public int Quantidade { get; init; }
public decimal PrecoUnitario { get; init; }
public decimal Subtotal => Quantidade * PrecoUnitario;
}
Vamos adicionar dois métodos à classe ItemPedidoDto
:
///
/// Converte uma coleção de entidades de itens de pedido em uma coleção de DTOs.
///
public static ICollection<ItemPedidoDto> MapFromItemPedidoEntityCollection(
ICollection<ItemPedidoEntity> itensPedido
)
{
var resultado = new List<ItemPedidoDto>();
foreach (var itemPedidoEntity in itensPedido)
{
resultado.Add(
new ItemPedidoDto
{
Id = itemPedidoEntity.Id,
DescricaoProduto = itemPedidoEntity.Produto.Descricao,
Quantidade = itemPedidoEntity.Quantidade,
PrecoUnitario = itemPedidoEntity.PrecoUnitario
}
);
}
return resultado;
}
///
/// Converte uma coleção de DTOs de itens de pedido em uma coleção de entidades.
///
public static ICollection<ItemPedidoEntity> MapToItemPedidoEntityCollection(
ICollection<ItemPedidoDto> itensPedido
)
{
var resultado = new List<ItemPedidoEntity>();
foreach (var itemPedidoDto in itensPedido)
{
resultado.Add(new ItemPedidoEntity
{
Id = itemPedidoDto.Id,
Quantidade = itemPedidoDto.Quantidade,
PrecoUnitario = itemPedidoDto.PrecoUnitario
});
}
return resultado;
}
E mais dois métodos na classe PedidoDto
, utilizando os anteriores:
///
/// Converte uma entidade de pedido em seu respectivo DTO.
///
public static PedidoDto MapFromPedidoEntity(PedidoEntity pedido) =>
new()
{
Id = pedido.Id,
DataPedido = pedido.DataPedido,
Itens = ItemPedidoDto.MapFromItemPedidoEntityCollection(pedido.Itens),
ValorTotal = pedido.ValorTotal
};
///
/// Converte uma instância de DTO de pedido em sua respectiva entidade.
///
public PedidoEntity MapToPedidoEntity() =>
new()
{
Id = Id,
DataPedido = DataPedido,
Itens = ItemPedidoDto.MapToItemPedidoEntityCollection(Itens),
ValorTotal = ValorTotal
};
Essa abordagem pode parecer mais "verbosa", mas o ganho em previsibilidade e clareza compensa. Cada transformação está explícita, facilitando a manutenção, testes e principalmente a depuração — algo crítico em projetos maiores. No fim das contas, escrever o mapeamento à mão pode parecer um pequeno esforço extra, mas é um investimento que retorna em produtividade a longo prazo.
Conclusão
Em contextos menores ou com mapeamentos triviais, o AutoMapper continua sendo uma ferramenta válida. Porém, em projetos mais complexos, os custos de performance e manutenção já eram consideráveis — e agora, com a transição para um modelo pago, a decisão de evitá-lo ganha ainda mais força.
Adotar uma abordagem manual, centrando os mapeamentos nos próprios DTOs, tem se mostrado mais eficiente em termos de clareza, controle e colaboração em equipe. É um caminho mais explícito e menos mágico — e que, no fim das contas, pode resultar em mais produtividade e menos dor de cabeça.
E você, ainda usa AutoMapper nos seus projetos? Já sentiu esses impactos ou encontrou outra solução mais equilibrada? Me conte nos comentários.
Até o próximo artigo!
Precisa de apoio para decidir o futuro dos seus projetos — manter, evoluir ou começar do zero? Fale com a gente!
Nosso time de especialistas está pronto para ajudar você a enfrentar esses e outros desafios tecnológicos.
Referências