AutoMapper для функций между типами селекторов - PullRequest
7 голосов
/ 15 сентября 2011

У меня есть два типа: Cat и Dog.Я хотел бы выбрать кошек, используя Func<Dog, bool>.Для этого мне нужен способ отобразить свойства из Cat в Dog в каком-либо типе сопоставления (аналогично тому, как AutoMapper отображает свойства из одного объекта в объект другого типа).

Я представляю себе что-то вроде этого:

public Cat GetCat(Func<Dog, bool> selector)
{
    Func<Cat, bool> mappedSelector = getMappedSelector(selector);
    return _catRepository.Get(mappedSelector);
}

private Func<Cat, bool> getMappedSelector(Func<Dog, bool> selector)
{
    //some code here to map from one function type to another

    //something like AutoMapper would be sweet... 
    //something that I can configure how I want the properties to be mapped.
}

Либо что-то уже есть, либо должно быть.

Ответы [ 2 ]

14 голосов
/ 15 сентября 2011

Вот решение с использованием AutoMapper:

Func<Cat, bool> GetMappedSelector(Func<Dog, bool> selector)
{
    Func<Cat, Dog> mapper = Mapper.CreateMapExpression<Cat, Dog>().Compile();
    Func<Cat, bool> mappedSelector = cat => selector(mapper(cat));
    return mappedSelector;
}

ОБНОВЛЕНИЕ: Прошло 1,5 года с тех пор, как я впервые ответил на это, и я решил, что сейчас расширю свой ответ, так как люди спрашивают, как это сделать, когда у вас есть выражение в отличие от делегата .

Решение в принципе то же самое - нам нужно иметь возможность объединить две функции (selector и mapper) в одну функцию. К сожалению, поскольку в C # нет способа «вызвать» одно выражение из другого (как мы могли бы с делегатами), мы не можем напрямую представить это в коде. Например, следующий код не удастся скомпилировать:

Expression<Func<Cat, bool>> GetMappedSelector(Expression<Func<Dog, bool>> selector)
{
    Expression<Func<Cat, Dog>> mapper = Mapper.CreateMapExpression<Cat, Dog>();
    Expression<Func<Cat, bool>> mappedSelector = cat => selector(mapper(cat));
    return mappedSelector;
}

Следовательно, единственный способ создать нашу составную функцию - это построить дерево выражений самостоятельно, используя System.Linq.Expressions классы.

Что нам действительно нужно сделать, так это изменить тело функции selector так, чтобы все экземпляры ее параметра были заменены телом функции mapper. Это станет телом нашей новой функции, которая будет принимать параметр mapper.

Чтобы заменить параметр, я создал подкласс ExpressionVisitor класса, который может перемещаться по дереву выражений и заменять один параметр произвольным выражением:

class ParameterReplacer : ExpressionVisitor
{
    private ParameterExpression _parameter;
    private Expression _replacement;

    private ParameterReplacer(ParameterExpression parameter, Expression replacement)
    {
        _parameter = parameter;
        _replacement = replacement;
    }

    public static Expression Replace(Expression expression, ParameterExpression parameter, Expression replacement)
    {
        return new ParameterReplacer(parameter, replacement).Visit(expression);
    }

    protected override Expression VisitParameter(ParameterExpression parameter)
    {
        if (parameter == _parameter)
        {
            return _replacement;
        }
        return base.VisitParameter(parameter);
    }
}

Затем я создал метод расширения Compose(), который использует посетителя для составления двух лямбда-выражений, внешнего и внутреннего:

public static class FunctionCompositionExtensions
{
    public static Expression<Func<X, Y>> Compose<X, Y, Z>(this Expression<Func<Z, Y>> outer, Expression<Func<X, Z>> inner)
    {
        return Expression.Lambda<Func<X ,Y>>(
            ParameterReplacer.Replace(outer.Body, outer.Parameters[0], inner.Body),
            inner.Parameters[0]);
    }
}

Теперь, имея всю эту инфраструктуру, мы можем изменить наш метод GetMappedSelector() для использования нашего расширения Compose():

Expression<Func<Cat, bool>> GetMappedSelector(Expression<Func<Dog, bool>> selector)
{
    Expression<Func<Cat, Dog>> mapper = Mapper.CreateMapExpression<Cat, Dog>();
    Expression<Func<Cat, bool>> mappedSelector = selector.Compose(mapper);
    return mappedSelector;
}

Я создал простое консольное приложение , чтобы проверить это. Надеюсь, мое объяснение не было слишком запутанным; но, к сожалению, на самом деле не существует более простого подхода к тому, что вы пытаетесь сделать. Если вы все еще в замешательстве, по крайней мере, вы можете повторно использовать мой код и по достоинству оценить нюансы и сложности работы с деревьями выражений!

2 голосов
/ 21 июня 2013

@ luksan, спасибо за вдохновение!Ваше решение не решило мою проблему, но заставило меня задуматься.Поскольку мне нужно было передать переведенное выражение в IQueryable.OrderBy (), использование метода перевода из внутреннего выражения не сработало.Но я придумал решение, которое будет работать в обоих случаях, а также будет проще в реализации.Он также универсален, поэтому может быть повторно использован для любых отображаемых типов.Вот код:

private Expression<Func<TDestination, TProperty>> GetMappedSelector<TSource, TDestination, TProperty>(Expression<Func<TSource, TProperty>> selector)
{
    var map = Mapper.FindTypeMapFor<TSource, TDestination>();

    var mInfo = ReflectionHelper.GetMemberInfo(selector);

    if (mInfo == null)
    {
        throw new Exception(string.Format(
            "Can't get PropertyMap. \"{0}\" is not a member expression", selector));
    }

    PropertyMap propmap = map
        .GetPropertyMaps()
        .SingleOrDefault(m => 
            m.SourceMember != null && 
            m.SourceMember.MetadataToken == mInfo.MetadataToken);

    if (propmap == null)
    {
        throw new Exception(
            string.Format(
            "Can't map selector. Could not find a PropertyMap for {0}", selector.GetPropertyName()));
    }

    var param = Expression.Parameter(typeof(TDestination));
    var body = Expression.MakeMemberAccess(param, propmap.DestinationProperty.MemberInfo);
    var lambda = Expression.Lambda<Func<TDestination, TProperty>>(body, param);

    return lambda;
}

Вот код ReflectionHelper (используется только для сохранения кода выше)

private static class ReflectionHelper
{
    public static MemberInfo GetMemberInfo(Expression memberExpression)
    {
        var memberExpr = memberExpression as MemberExpression;

        if (memberExpr == null && memberExpression is LambdaExpression)
        {
            memberExpr = (memberExpression as LambdaExpression).Body as MemberExpression;
        }

        return memberExpr != null ? memberExpr.Member : null;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...