AutoMapper for Func在选择器类型之间

我有两种类型: Dog 。 我想用Func来选择Cats。 要做到这一点,我需要一种方法在某种映射器中将属性从Cat映射到Dog(类似于AutoMapper将属性从一个对象映射到另一个对象类型)。

我想象的是这样的:

 public Cat GetCat(Func selector) { Func mappedSelector = getMappedSelector(selector); return _catRepository.Get(mappedSelector); } private Func getMappedSelector(Func 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. } 

要么已经有了这样或应该有的东西。

这是使用AutoMapper的解决方案:

 Func GetMappedSelector(Func selector) { Func mapper = Mapper.CreateMapExpression().Compile(); Func mappedSelector = cat => selector(mapper(cat)); return mappedSelector; } 

更新:我第一次回答这个问题已有1。5年了,我想我现在会扩展我的答案,因为当你有一个表达而不是代表时,人们会问你如何做到这一点。

解决方案原则上是相同的 – 我们需要能够将两个函数( selectormapper )组合成单个函数。 不幸的是,由于C#中没有办法从另一个表达式“调用”一个表达式(就像我们可以使用委托),我们无法在代码中直接表示它。 例如,以下代码将无法编译:

 Expression> GetMappedSelector(Expression> selector) { Expression> mapper = Mapper.CreateMapExpression(); Expression> 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() ,它使用visitor来组成两个lambda表达式,一个外部和一个内部:

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

现在,有了所有基础设施,我们可以修改GetMappedSelector()方法以使用我们的Compose()扩展:

 Expression> GetMappedSelector(Expression> selector) { Expression> mapper = Mapper.CreateMapExpression(); Expression> mappedSelector = selector.Compose(mapper); return mappedSelector; } 

我创建了一个简单的控制台应用程序来测试它。 希望我的解释不会太混淆; 但遗憾的是,没有一种更简单的方法来做你想要做的事情。 如果你仍然完全困惑,至少你可以重用我的代码,并对表达树处理的细微差别和复杂性有所了解!

@luksan,感谢您的灵感! 你的解决方案没有解决我的问题,但让我思考。 由于我需要将已翻译的表达式传递给IQueryable.OrderBy(),因此使用内部表达式转换方法不起作用。 但我提出了一个解决方案,可以解决这两种情况,也更容易实现。 它也是通用的,因此可以重用于任何映射类型。 这是代码:

 private Expression> GetMappedSelector(Expression> selector) { var map = Mapper.FindTypeMapFor(); 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>(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; } }