Mapeando simple objects e complex objects com AutoMapper

Bom, para quem não sabe o que é o AutoMapper, segue uma definição retirada do próprio site do projeto:

“AutoMapper is a simple little library built to solve a deceptively complex problem – getting rid of code that mapped one object to another.”

Ou seja, é um biblioteca criada para mapeamento entre objetos.

Ao longo do artigo irei demonstrar exemplos de mapeamento entre objetos simples e complexos, além de demonstrar como configurar o mapeamento e exibir os prós e contras (no meu ponto de vista) quanto ao recurso/biblioteca.

Para o exemplo estarei utilizando Linq To SQL com um projeto em ClassLibrary.

Vamos considerar as seguintes informações:

Imagem 1

Como é possível observar, temos 3 tabelas (tbTipo, tbGrupo e tbProduto)

Nosso DBML fica da seguinte forma:

Imagem 2

Irei criar duas classes DTO para o mapeamento entre nosso modelo, Grupo_DTO e Produto_DTO, veja:

public class Grupo_DTO
{
    public string nome { get; set; }
}

public class Produto_DTO
{
    public string nome { get; set; }
    public decimal valor { get; set; }
    public DateTime data { get; set; }
    public string nomeTipo { get; set; }
    public string nomeGrupo { get; set; }
}

Mapeando objetos simples

Após efetuar o download da biblioteca, devemos utilizar o namespace AutoMapper.
O mapeamento com o AutoMapper é bem simples, basicamente, é necessário que seja definido como o mapeamento será efetuado, em seguida já é possível realizar efetivamente o mapeamento. Veja um exemplo simples, utilizando as classes Grupo e Grupo_DTO:

//Define como o mapeamento será efetuado
Mapper.CreateMap<Grupo, Grupo_DTO>();

//Realiza o mapeamento
Grupo g = _db.Grupos.FirstOrDefault();
Grupo_DTO grupoMapeado = Mapper.Map<Grupo, Grupo_DTO>(g);

Desta forma o mapeamento já é efetuado, porém, podemos melhorar algumas coisas :) . Vamos criar um método genérico, que realiza o mapeamento para qualquer objeto origem/destino:

public static IList<TDestino> RealizaMapeamento<TOrigem, TDestino>()
    where TOrigem : class
    where TDestino : class
{
    return Mapper.Map<IQueryable<TOrigem>, IList<TDestino>>(_db.GetTable<TOrigem>());
}

public static TDestino RealizaMapeamento<TOrigem, TDestino>(TOrigem objOrigem)
    where TOrigem : class
    where TDestino : class
{
    return Mapper.Map<TOrigem, TDestino>(objOrigem);
}

Agora podemos utilizar algo como:

//Exemplo 1
Grupo g = _db.Grupos.FirstOrDefault();
Grupo_DTO grupoMapeado = RealizaMapeamento<Grupo, Grupo_DTO>(g);

//Exemplo 2
IList<Grupo_DTO> listaMapeada1 = Mapper.Map<IQueryable<Grupo>, IList<Grupo_DTO>>(_db.Grupos);

//Exemplo 3
IList<Grupo_DTO> listaMapeada2 = RealizaMapeamento<Grupo, Grupo_DTO>();

Mapeando objetos complexos

Analisando a classe Produto_DTO, podemos observar que algumas propriedades são diferentes quanto a classe Produto, como nomeTipo, nomeGrupo e data. Para que estas propriedades sejam preenchidas ao efetuar o mapeamento, devemos configura-las, observe:

Mapper.CreateMap<Produto, Produto_DTO>()
    .ForMember(dest => dest.data, ori => ori.MapFrom(src => src.dataCadastro))
    .ForMember(dest => dest.nomeGrupo, ori => ori.MapFrom(src => src.Grupo.nome))
    .ForMember(dest => dest.nomeTipo, ori => ori.MapFrom(src => (src.Tipo != null) ? src.Tipo.nome : string.Empty));

Veja que:

  • O primeiro ForMember define que a propriedade dataCadastro vai preencher a propriedade data;
  • O segundo ForMember define que a propriedade nome presente no objeto Grupo vai preencher a propriedade nomeGrupo;
  • O ultimo ForMember define que a propriedade nome presente no objeto Tipo vai preencher a propriedade nomeTipo, porém, este verifica antes se o objeto Tipo possui valor.

Agora podemos realizar o mapeamento como anteriormente:

IList<Produto_DTO> listaMapeada3 = RealizaMapeamento<Produto, Produto_DTO>();

Observe que o mapeamento é efetuado com sucesso:

Imagem 3

Porém…

Vá com calma, o AutoMapper não é perfeito

Isso mesmo, vou demonstrar o porque abrindo o SQL Profiler. Primeiramente vou executar uma query que realiza o mapeamento, porém apenas com Linq, veja:

IList<Produto_DTO> lista = (from p in _db.Produtos
                            select new Produto_DTO()
                            {
                                nome = p.nome,
                                data = p.dataCadastro.Value,
                                valor = p.valor.Value,
                                nomeGrupo = p.Grupo.nome,
                                nomeTipo = (p.Tipo != null ? p.Tipo.nome : string.Empty)
                            }).ToList();

Ao executarmos esta query o profiler do SQL server exibe o que realmente foi executado no BD:

Imagem 4

Veja que foi executada apenas uma query. Ótimo. :)

Agora vamos limpar o profiler e executar apenas a rotina que efetua o mapeamento com o AutoMapper, observe agora o resultado:

IList<Produto_DTO> listaMapeada3 = RealizaMapeamento<Produto, Produto_DTO>();

Imagem 5

Veja o resutado, foram executadas diversas queries, na verdade o AutoMapper efetua uma query para cada join de seu mapeamento, no caso, com as tabelas tbTipo e tbGrupo. O criador do AutoMapper (Jimmy Bogard) está ciente do problema, e o mesmo até criou um post em blog comentando, explicando e demonstrando um solução paleativa quanto ao problema, (Solução essa que seria basicamente a criação de um método de extensão para IQueryable, onde seja permitido especificar exatamente qual será o retorno quando a consulta é executada)

Outro ótimo artigo sobre este erro pode ser visto aqui.

Bom, por hoje era isso e use o AutoMapper com moderação. ;)

 

This entry was posted in AutoMapper, C#, Design Pattern, Plugins & Componentes and tagged , , . Bookmark the permalink.

2 Responses to Mapeando simple objects e complex objects com AutoMapper

  1. Muito boa postagem, especialmente falando do efeito colateral, a maioria das pessoas esquecem de relatar que nem tudo são flores.

    Parabéns.

  2. Pingback: URL

Deixe um Comentário

O seu endereço de email não será publicado Campos obrigatórios são marcados *

*


*

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>