AutoMapper for Func's between selector types
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!
@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;
}
}