AutoMapper pour Func entre les types de sélecteur

J’ai deux types: Cat et Dog . Je voudrais sélectionner les chats en utilisant un Func . Pour ce faire, j’ai besoin d’un moyen de mapper les propriétés de Cat à Dog dans une sorte de mappeur (similaire à la façon dont AutoMapper mappe les propriétés d’un object à un autre type d’object).

J’imagine quelque chose comme ça:

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

Soit il y a déjà quelque chose qui fait cela ou il devrait y avoir.

Voici une solution utilisant AutoMapper:

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

MISE À JOUR: Cela fait un an et demi que je réponds à cette question et je me suis dit que je devrais développer ma réponse maintenant, car les gens demandent comment faire cela lorsque vous avez une expression par opposition à un délégué.

La solution est la même en principe – nous devons pouvoir composer les deux fonctions ( selector et mapper ) en une seule fonction. Malheureusement, comme il n’ya aucun moyen dans C # d’appeler une expression d’une autre (comme avec les delegates), nous ne pouvons pas la représenter directement dans le code. Par exemple, la compilation du code suivant échouera:

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

Par conséquent, la seule façon de créer notre fonction composée consiste à créer nous-mêmes l’ arbre des expressions à l’aide des classes System.Linq.Expressions .

Ce que nous devons vraiment faire est de modifier le corps de la fonction de selector sorte que toutes les occurrences de son paramètre soient remplacées par le corps de la fonction mapper . Cela deviendra le corps de notre nouvelle fonction, qui acceptera le paramètre du mapper .

Pour remplacer le paramètre, j’ai créé une sous-classe de la classe ExpressionVisitor qui peut traverser un arbre d’expression et remplacer un paramètre unique par une expression arbitraire:

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

Ensuite, j’ai créé une méthode d’extension, Compose() , qui utilise le visiteur pour composer deux expressions lambda, une externe et une interne:

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

Maintenant, avec toute cette infrastructure en place, nous pouvons modifier notre méthode GetMappedSelector() pour utiliser notre extension Compose() :

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

J’ai créé une application console simple pour tester cela. J’espère que mon explication n’était pas trop obscure; mais malheureusement, il n’ya pas vraiment d’approche plus simple pour faire ce que vous essayez de faire. Si vous êtes toujours totalement confus, vous pouvez au moins réutiliser mon code et vous comprendrez mieux les nuances et les complexités liées au traitement des arbres d’expression!

@luksan, merci pour l’inspiration! Votre solution n’a pas résolu mon problème, mais m’a fait réfléchir. Étant donné que j’avais besoin de transmettre l’expression traduite à IQueryable.OrderBy (), l’utilisation de la méthode de traduction d’expression interne ne fonctionnait pas. Mais j’ai proposé une solution qui fonctionne dans les deux cas et qui est également plus simple à mettre en œuvre. Il est également générique et peut donc être réutilisé pour tous les types mappés. Voici le code:

 private Expression> GetMappedSelector(Expression> selector) { var map = Mapper.FindTypeMapFor(); var mInfo = ReflectionHelper.GetMemberInfo(selector); if (mInfo == null) { throw new Exception(ssortingng.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( ssortingng.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; } 

Voici le code ReflectionHelper (utilisé juste pour garder le code ci-dessus plus propre)

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