Mapeamento de Expressões com AutoMapper em C#

Expression<Func<Entity, bool>> expr = e => e.Propriedade == "Valor";

Deve ser convertida para:

Expression<Func<Dto, bool>> expr = e => e.Propriedade == "Valor";

A solução envolve analisar a árvore de expresssão e substituir os nós com base no mapeamento. Abaixo está uma implementação genérica para essa conversão.

Impelmentação da Extensão

Uma classe estática de extensão é criada para lidar com a composição de expressões, incluindo cache para desempenho.

using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper;
using AutoMapper.QueryableExtensions;

namespace Expressoes.Core
{
    public static class ConversorExpressoesExtensions
    {
        private static readonly ConcurrentDictionary<ChaveTipo, TuplaMetodoExpressao> CacheExpressoes =
            new ConcurrentDictionary<ChaveTipo, TuplaMetodoExpressao>();

        private static readonly MethodInfo MetodoComposicao = typeof(ConversorExpressoesExtensions)
            .GetMethod("Compor", BindingFlags.NonPublic | BindingFlags.Static);

        public static Expression<Func<TAlvo, bool>> ConverterExpressao<TOrigem, TAlvo>(
            this Expression<Func<TOrigem, bool>> expressaoOrigem)
        {
            var chave = new ChaveTipo(typeof(TOrigem), typeof(TAlvo));
            var tupla = CacheExpressoes.GetOrAdd(chave, _ =>
            {
                var expressaoMapeamento = Mapper.Engine.CreateMapExpression<TAlvo, TOrigem>();
                var metodo = MetodoComposicao.MakeGenericMethod(typeof(TAlvo), typeof(bool), typeof(TOrigem));
                return new TuplaMetodoExpressao(metodo, expressaoMapeamento);
            });

            return tupla.Metodo.Invoke(null, new object[] { expressaoOrigem, tupla.Expressao }) as
                Expression<Func<TAlvo, bool>>;
        }

        private static Expression<Func<TX, TY>> Compor<TX, TY, TZ>(
            Expression<Func<TZ, TY>> expressaoExterna, Expression<Func<TX, TZ>> expressaoInterna)
        {
            var corpoSubstituido = VisitanteExpressaoHelper.Substituir(
                expressaoExterna.Body, expressaoExterna.Parameters[0], expressaoInterna.Body);
            return Expression.Lambda<Func<TX, TY>>(corpoSubstituido, expressaoInterna.Parameters[0]);
        }

        private class ChaveTipo
        {
            public Type TipoOrigem { get; }
            public Type TipoAlvo { get; }

            public ChaveTipo(Type tipoOrigem, Type tipoAlvo)
            {
                TipoOrigem = tipoOrigem;
                TipoAlvo = tipoAlvo;
            }

            public override int GetHashCode() => TipoOrigem.GetHashCode() ^ TipoAlvo.GetHashCode();
            public override bool Equals(object obj) =>
                obj is ChaveTipo outro && TipoOrigem == outro.TipoOrigem && TipoAlvo == outro.TipoAlvo;
        }

        private class TuplaMetodoExpressao
        {
            public MethodInfo Metodo { get; }
            public Expression Expressao { get; }

            public TuplaMetodoExpressao(MethodInfo metodo, Expression expressao)
            {
                Metodo = metodo;
                Expressao = expressao;
            }
        }
    }

    internal class VisitanteExpressaoHelper : ExpressionVisitor
    {
        private readonly ParameterExpression _parametroOriginal;
        private readonly Expression _substituto;

        private VisitanteExpressaoHelper(ParameterExpression parametroOriginal, Expression substituto)
        {
            _parametroOriginal = parametroOriginal;
            _substituto = substituto;
        }

        public static Expression Substituir(Expression expressao, ParameterExpression parametroOriginal, Expression substituto)
        {
            return new VisitanteExpressaoHelper(parametroOriginal, substituto).Visit(expressao);
        }

        protected override Expression VisitParameter(ParameterExpression no)
        {
            return no == _parametroOriginal ? _substituto : base.VisitParameter(no);
        }
    }
}

Exemplo de Uso

Configure o AutoMapper e utilize a extensão para converter expressões entre tipos.

Mapper.CreateMap<Entidade, Dto>();
Mapper.CreateMap<Dto, Entidade>();

var lista = new List<Dto>
{
    new Dto { Nome = "Teste" },
    new Dto { Nome = "Exemplo" },
    new Dto { Nome = "Outro" }
};

Expression<Func<Entidade, bool>> expressaoEntidade = e => e.Nome == "Exemplo";
var expressaoDto = expressaoEntidade.ConverterExpressao<Entidade, Dto>();

var resultado = lista.Where(expressaoDto.Compile());
// resultado contém um elemento correspondente

Tags: AutoMapper CSharp ExpressionTrees DTO EntityFramework

Publicado em 6-21 04:56