Перевод дерева выражений из типа в другой тип со сложными отображениями

вдохновленныйэтот ответ Я пытаюсь сопоставить свойство класса модели с выражением, основанным на фактической сущности. Это два участвующих класса:

<code>public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Id { get; set; }
    public DateTime? BirthDate { get; set; }
    public int CustomerTypeId { get; set; }
}

public class CustomerModel
{
    ...
    public bool HasEvenId { get; set; }
}
</code>

Пример возможного выражения, которое я хотел бы преобразовать:

<code>Expression<Func<CustomerModel, bool>> from = model => model.HasEvenId;
Expression<Func<Customer, bool>> to = entity => ((entity.Id % 2) == 0);
</code>

Проблема заключается в том, что мне нужно предоставить конечную точку OData через ASP.NET WebAPI, но мне нужно выполнить некоторые операции над объектами, прежде чем я смогу их получить, поэтому мне нужен класс модели и необходимость переводить выражение на основе модели, которая Я мог получить запрос OData в выражении, основанном на сущности, который я использовал бы для запроса EF4.

Вот где я дошел до сих пор:

<code>private static readonly Dictionary<Expression, Expression> Mappings = GetMappings();

private static Dictionary<Expression, Expression> GetMappings()
{
    var mappings = new Dictionary<Expression, Expression>();

    var mapping = GetMappingFor((CustomerModel model) => model.HasEvenId, (Customer customer) => (customer.Id%2) == 0);
    mappings.Add(mapping.Item1, mapping.Item2);

    return mappings;
}

private static Tuple<Expression, Expression> GetMappingFor<TFrom, TTo, TValue>(Expression<Func<TFrom, TValue>> fromExpression, Expression<Func<TTo, TValue>> toExpression)
{
    MemberExpression fromMemberExpression = (MemberExpression) fromExpression.Body;
    return Tuple.Create<Expression, Expression>(fromMemberExpression, toExpression);
}

public static Expression<Func<TTo, bool>> Translate<TFrom, TTo>(Expression<Func<TFrom, bool>> expression, Dictionary<Expression, Expression> mappings = null)
{
    if (expression == null)
        return null;

    string parameterName = expression.Parameters[0].Name;

    parameterName = string.IsNullOrWhiteSpace(parameterName) ? "p" : parameterName;

    var param = Expression.Parameter(typeof(TTo), parameterName);
    var subst = new Dictionary<Expression, Expression> { { expression.Parameters[0], param } };
    ParameterChangeVisitor parameterChange = new ParameterChangeVisitor(parameterName);
    if (mappings != null)
        foreach (var mapp in mappings)
            subst.Add(mapp.Key, parameterChange.Visit(mapp.Value));

    var visitor = new TypeChangeVisitor(typeof(TFrom), typeof(TTo), subst);
    return Expression.Lambda<Func<TTo, bool>>(visitor.Visit(expression.Body), param);
}

public IQueryable<CustomerModel> Get()
{
    var filterExtractor = new ODataFilterExtractor<CustomerModel>();
    Expression<Func<CustomerModel, bool>> expression = filterExtractor.Extract(Request);
    Expression<Func<Customer, bool>> translatedExpression = Translate<CustomerModel, Customer>(expression, Mappings);

    IQueryable<Customer> query = _context.Customers;

    if (translatedExpression != null)
        query = query.Where(translatedExpression);

    var finalQuery = from item in query.AsEnumerable() 
                     select new CustomerModel()
                        {
                            FirstName = item.FirstName, 
                            LastName = item.LastName, 
                            Id = item.Id, 
                            BirthDate = item.BirthDate, 
                            CustomerTypeId = item.CustomerTypeId,
                            HasEvenId = (item.Id % 2 ) == 0
                        };

    return finalQuery.AsQueryable();
}
</code>

где:

ODataFilterExtractor is a class that extract the $filter expression from the RequestMessage we receive; ParameterChangeVisitor just changes all the ParameterExpression to a new one having the provided string as parameter name;

Кроме того, я изменил метод VisitMember ответа, связанного выше, таким образом:

<code>protected override Expression VisitMember(MemberExpression node)
{
    // if we see x.Name on the old type, substitute for new type
    if (node.Member.DeclaringType == _from)
    {
        MemberInfo toMember = _to.GetMember(node.Member.Name, node.Member.MemberType, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SingleOrDefault();
        if (toMember != null)
        {
            return Expression.MakeMemberAccess(Visit(node.Expression), toMember);
        }
        else
        {
            if (_substitutions.Select(kvp => kvp.Key).OfType<MemberExpression>().Any(me => me.Member.Equals(node.Member)))
            {
                MemberExpression key = _substitutions.Select(kvp => kvp.Key).OfType<MemberExpression>().Single(me => me.Member.Equals(node.Member));
                Expression value = _substitutions[key];

                // What to return here?
                return Expression.Invoke(value);
            }
        }
    }
    return base.VisitMember(node);
}
</code>

Спасибо за помощь.

Ответы на вопрос(2)

AutoMapper отобразить сложные типы и изменить полученный запрос выражения с помощью ExpressionTransformer до его выполнения. Я постараюсь объяснить с полным примером:

Model classes

Некоторые классы POCO только для хранения данных.

public enum CostUnitType
{
    None = 0,
    StockUnit = 1,
    MachineUnit = 2,
    MaintenanceUnit = 3
}

public class CostUnit
{
    public string CostUnitId { get; set; }
    public string WorkplaceId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }

    public CostUnitType Type { get; set; }

    public override string ToString()
    {
        return CostUnitId + " " + Name + " " + Type;
    }
}

public class StockUnit
{
    public string Id { get; set; }
    public string Name { get; set; }
}

public class MachineUnit
{
    public string Id { get; set; }
    public string Name { get; set; }
}

public class MaintenanceUnit
{
    public string Id { get; set; }
    public string Name { get; set; }
}

The ExpressionTransformer class

Большую часть времени можно использовать статический класс Mapper, но иногда вам нужно сопоставить одни и те же типы с разными конфигурациями, поэтому вам нужно явно использовать IMappingEngine, например:

var configuration = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
var engine = new MappingEngine(configuration);

Способ создания MappingEngine совсем не очевиден. Мне пришлось копаться в исходном коде, чтобы найти, как это было сделано.

public static class ExpressionTransformer
{
    private static readonly MappingEngine Mapper;

    /// <summary>
    /// Initializes the <see cref="ExpressionTransformer"/> class.
    /// Creates an instance of AutoMapper. Initializes mappings.
    /// </summary>
    static ExpressionTransformer()
    {
        ConfigurationStore configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);

        // Create mapping
        // Maps Id from StockUnit to CostUnitId from CostUnit
        configurationStore.CreateMap<StockUnit, CostUnit>()
            .ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
        // Maps Id from MachineUnit to CostUnitId from CostUnit
        configurationStore.CreateMap<MachineUnit, CostUnit>()
            .ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
        // Maps Id from MaintenanceUnit to CostUnitId from CostUnit
        configurationStore.CreateMap<MaintenanceUnit, CostUnit>()
            .ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));

        // Create instance of AutoMapper
        Mapper = new MappingEngine(configurationStore);
    }

    public static Expression<Func<TDestination, bool>> Tranform<TSource, TDestination>(Expression<Func<TSource, bool>> sourceExpression)
    {
        // Resolve mappings by AutoMapper for given types.
        var map = Mapper.ConfigurationProvider.FindTypeMapFor(typeof(TSource), typeof(TDestination));

        if (map == null)
        {
            throw new AutoMapperMappingException(string.Format("No Mapping found for {0} --> {1}.", typeof(TSource).Name, typeof(TDestination).Name));
        }

        // Transform from TSource to TDestination with specified mappings
        var visitor = new ParameterTypeVisitor<TSource, TDestination>(sourceExpression, map.GetPropertyMaps());
        var expression = visitor.Transform();

        return expression;
    }

    private class ParameterTypeVisitor<TSource, TDestination> : ExpressionVisitor
    {
        private readonly Dictionary<string, ParameterExpression> _parameters;
        private readonly Expression<Func<TSource, bool>> _expression;
        private readonly IEnumerable<PropertyMap> _maps;

        public ParameterTypeVisitor(Expression<Func<TSource, bool>> expression, IEnumerable<PropertyMap> maps)
        {
            _parameters = expression.Parameters
                .ToDictionary(p => p.Name, p => Expression.Parameter(typeof(TDestination), p.Name));

            _expression = expression;

            _maps = maps;
        }

        public Expression<Func<TDestination, bool>> Transform()
        {
            return (Expression<Func<TDestination, bool>>) Visit(_expression);
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Member.DeclaringType == typeof(TSource))
            {
                var memberName = node.Member.Name;
                var member = _maps.FirstOrDefault(p => typeof(TSource) == node.Expression.Type
                                                        && p.SourceMember.Name == memberName);
                if (member != null)
                {
                    // Return Property from TDestination
                    var expression = Visit(node.Expression);
                    return Expression.MakeMemberAccess(expression, member.DestinationProperty.MemberInfo);
                }
            }

            return base.VisitMember(node);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            var parameter = _parameters[node.Name];
       ,     return parameter;
        }

        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            var expression = Visit(node.Body);
            return Expression.Lambda(expression, _parameters.Select(x => x.Value));
        }
    }
}

Usage

Для преобразования выражения нам просто нужно вызвать метод Transform класса ExpressionTransformer.

            Expression<Func<StockUnit, bool>> stockQuery = unit => unit.Id == "0815" && unit.Name == "ABC";

            // Call Transform<TSource, TDestination> method.
            Expression<Func<CostUnit, bool>> costUnitQuery = ExpressionTransformer.Tranform<StockUnit, CostUnit>(stockQuery);

Result

Resulting expressions

но это делает свое дело,

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Id { get; set; }
    public DateTime? BirthDate { get; set; }
    public int CustomerTypeId { get; set; }
}

public class CustomerModel
{
    public string FullName { get; set; }
    public bool HasEvenId { get; set; }
}

sealed class AToBConverter<TA, TB> : ExpressionVisitor
{
    private readonly Dictionary<ParameterExpression, ParameterExpression> _parameters = new Dictionary<ParameterExpression, ParameterExpression>();
    private readonly Dictionary<MemberInfo, LambdaExpression> _mappings;

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == typeof(TA))
        {
            ParameterExpression parameter;
            if (!this._parameters.TryGetValue(node, out parameter))
            {
                this._parameters.Add(node, parameter = Expression.Parameter(typeof(TB), node.Name));
            }
            return parameter;
        }
        return node;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression == null || node.Expression.Type != typeof(TA))
        {
            return base.VisitMember(node);
        }
        Expression expression = this.Visit(node.Expression);
        if (expression.Type != typeof(TB))
        {
            throw new Exception("Whoops");
        }
        LambdaExpression lambdaExpression;
        if (this._mappings.TryGetValue(node.Member, out lambdaExpression))
        {
            return new SimpleExpressionReplacer(lambdaExpression.Parameters.Single(), expression).Visit(lambdaExpression.Body);
        }
        return Expression.Property(
            expression,
            node.Member.Name
        );
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        return Expression.Lambda(
            this.Visit(node.Body),
            node.Parameters.Select(this.Visit).Cast<ParameterExpression>()
        );
    }

    public AToBConverter(Dictionary<MemberInfo, LambdaExpression> mappings)
    {
        this._mappings = mappings;
    }
}

sealed class SimpleExpressionReplacer : ExpressionVisitor
{
    private readonly Expression _replacement;
    private readonly Expression _toFind;

    public override Expression Visit(Expression node)
    {
        return node == this._toFind ? this._replacement : base.Visit(node);
    }

    public SimpleExpressionReplacer(Expression toFind, Expression replacement)
    {
        this._toFind = toFind;
        this._replacement = replacement;
    }
}

class Program 
{
    private static Dictionary<MemberInfo, LambdaExpression> GetMappings()
    {
        var mappings = new Dictionary<MemberInfo, LambdaExpression>();
        var mapping = GetMappingFor(model => model.HasEvenId, customer => (customer.Id % 2) == 0);
        mappings.Add(mapping.Item1, mapping.Item2);
        mapping = GetMappingFor(model => model.FullName, customer => customer.FirstName + " " + customer.LastName);
        mappings.Add(mapping.Item1, mapping.Item2);
        return mappings;
    }

    private static Tuple<MemberInfo, LambdaExpression> GetMappingFor<TValue>(Expression<Func<CustomerModel, TValue>> fromExpression, Expression<Func<Customer, TValue>> toExpression)
    {
        return Tuple.Create(((MemberExpression)fromExpression.Body).Member, (LambdaExpression)toExpression);
    }

    static void Main()
    {
        Expression<Func<CustomerModel, bool>> source = model => model.HasEvenId && model.FullName == "John Smith";
        Expression<Func<Customer, bool>> desiredResult = model => (model.Id % 2) == 0 && (model.FirstName + " " + model.LastName) == "John Smith";
        Expression output = new AToBConverter<CustomerModel, Customer>(GetMappings()).Visit(source);
        Console.WriteLine("The two expressions do {0}match.", desiredResult.ToString() == output.ToString() ? null : "not ");
        Console.ReadLine();
    }
}

ВАШ ОТВЕТ НА ВОПРОС