Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper for Func's between selector types

Tags:

c#

linq

I have two types: Cat and Dog. I'd like to select Cats using a Func<Dog, bool>. To do that, I need a way to map the properties from Cat to Dog in some sort of mapper (similar to how AutoMapper maps properties from one object to another type of object).

I'm imagining something like this:

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.
}

Either there's already something that does this or there should be.

like image 865
Byron Sommardahl Avatar asked Sep 15 '11 00:09

Byron Sommardahl


2 Answers

Here's a solution using 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;
}

UPDATE: It's been 1.5 years since I first answered this, and I figured I'd expand on my answer now since people are asking how to do this when you have an expression as opposed to a delegate.

The solution is the same in principle - we need to be able to compose the two functions (selector and mapper) into a single function. Unfortunately, since there's no way in C# to "call" one expression from another (like we could with delegates), we can't directly represent this in code. For example, the following code will fail to compile:

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;
}

The only way to create our composed function, therefore, is to build up the expression tree ourselves using the System.Linq.Expressions classes.

What we really need to do is to modify the body of the selector function so that all instances of its parameter are replaced by the body of the mapper function. This will become the body of our new function, which will accept mapper's parameter.

To replace the parameter I created a subclass of ExpressionVisitor class that can traverse an expression tree and replace a single parameter with an arbitrary expression:

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);
    }
}

Then I created an extension method, Compose(), that uses the visitor to compose two lambda expressions, an outer and an inner:

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]);
    }
}

Now, with all that infrastructure in place, we can modify our GetMappedSelector() method to use our Compose() extension:

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;
}

I created a simple console application to test this out. Hopefully, my explanation was not too obfuscated; but unfortunately, there isn't really a simpler approach to doing what you're trying to do. If you are still totally confused, at least you can reuse my code and have gained an appreciation for the nuances and complexities of dealing with expression trees!

like image 174
luksan Avatar answered Oct 05 '22 23:10

luksan


@luksan, thanks for the inspiration! Your solution didn't solve my problem, but got me thinking. Since I needed to pass the translated expression to IQueryable.OrderBy(), using the inside-expression translation approach didn't work. But I came up with a solution that will work on both cases and is also simpler to implement. It's also generic so can be reused for any mapped types. Here is the code:

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;
}

Here is the ReflectionHelper code (used just to keep the code above cleaner)

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;
    }
}
like image 21
Saulo Vallory Avatar answered Oct 06 '22 00:10

Saulo Vallory