AutoMapper est l'un des principaux outils utilisés dans le développement d'applications d'entreprise, je souhaite donc écrire le moins de code possible lors de la définition du mappage d'entités.
Je n'aime pas la duplication dans MapFrom avec des projections larges.
CreateMap<Pupil, PupilDto>() .ForMember(x => x.Name, s => s.MapFrom(x => x.Identity.Passport.Name)) .ForMember(x => x.Surname, s => s.MapFrom(x => x.Identity.Passport.Surname)) .ForMember(x => x.Age, s => s.MapFrom(x => x.Identity.Passport.Age)) .ForMember(x => x.Number, s => s.MapFrom(x => x.Identity.Passport.Number))
Je voudrais le réécrire comme ceci:
CreateMap<Pupil, PupilDto>() .From(x=>x.IdentityCard.Passport).To()
Projectto
AutoMapper peut créer un mappage à la fois en mémoire et le traduire en SQL, il termine Expression, projetant en DTO selon les règles que vous avez décrites dans les profils.
EntityQueryable.Select(dtoPupil => new PupilDto() { Name = dtoPupil.Identity.Passport, Surname = dtoPupil.Identity.Passport.Surname})
80% du mappage que je dois écrire est le mappage qui complète l'expression à partir d'IQueryble.
C'est très pratique:
public ActionResult<IEnumerable<PupilDto>> GetAdultPupils(){ var result = _context.Pupils .Where(x=>x.Identity.Passport.Age >= 18 && ...) .ProjectTo<PupilDto>().ToList(); return result; }
Dans un style déclaratif, nous avons formé une requête sur la table Élèves, ajouté du filtrage, projeté dans le DTO souhaité et renvoyé au client, afin que vous puissiez écrire toutes les méthodes de lecture d'une interface CRUD simple. Et tout cela se fera au niveau de la base de données.
Certes, dans les applications sérieuses, de telles actions sont peu susceptibles de satisfaire les clients.
AutoMapper'a
1) Il est très détaillé, avec un mappage "large", vous devez écrire des règles qui ne tiennent pas sur une seule ligne de code.
Les profils grandissent et se transforment en archives de code qui est écrit une fois et ne change que lors de la refactorisation des noms.
2) Si vous utilisez le mappage selon la convention, le nom est perdu
propriétés en DTO:
public class PupilDto {
3) Manque de sécurité de type
1 et 2 sont des moments désagréables, mais vous pouvez les supporter, mais avec le manque de sécurité de type lors de l'enregistrement, il est déjà plus difficile à supporter, cela ne devrait pas être compilé:
Nous souhaitons recevoir des informations sur ces erreurs au stade de la compilation et non au moment de l'exécution.
Utiliser des enveloppes d'extention pour éliminer ces points.
Écrire un wrapper
Pourquoi l'inscription doit-elle être écrite de cette façon?
CreateMap<Pupil, PupilDto>() .ForMember(x => x.Name, s => s.MapFrom(x => x.Identity.Passport.Name)) .ForMember(x => x.Surname, s => s.MapFrom(x => x.Identity.Passport.Surname)) .ForMember(x => x.Age, s => s.MapFrom(x => x.Identity.Passport.Age)) .ForMember(x => x.House, s => s.MapFrom(x => x.Address.House)) .ForMember(x => x.Street, s => s.MapFrom(x => x.Address.Street)) .ForMember(x => x.Country, s => s.MapFrom(x => x.Address.Country)) .ForMember(x => x.Surname, s => s.MapFrom(x => x.Identity.Passport.Age)) .ForMember(x => x.Group, s => s.MapFrom(x=>x.EducationCard.StudyGroup.Number))
Beaucoup plus concis:
CreateMap<Pupil,PupilDto>()
La méthode To accepte les tuples si vous devez spécifier des règles de mappage
IMapping <TSource, TDest> est l'interface du constructeur automatique dans laquelle les méthodes ForMember, ForAll () sont définies ... toutes ces méthodes renvoient ceci (Fluent Api).
Nous retournerons le wrapper pour se souvenir de la méthode Expression from From
public static MapperExpressionWrapper<TSource, TDest, TProjection> From<TSource, TDest, TProjection> (this IMappingExpression<TSource, TDest> mapping, Expression<Func<TSource, TProjection>> expression) => new MapperExpressionWrapper<TSource, TDest, TProjection>(mapping, expression);
Maintenant, le programmeur, après avoir écrit la méthode From, verra immédiatement la surcharge de la méthode To , ainsi nous lui dirons l'API, dans de tels cas, nous pouvons réaliser tous les charmes des méthodes d'extension, nous avons étendu le comportement sans avoir accès en écriture aux sources de l'automappeur
Nous caractérisons
L'implémentation d'une méthode typée To est plus compliquée.
Essayons de concevoir cette méthode, nous devons la diviser autant que possible et retirer toute la logique des autres méthodes. Acceptez immédiatement que nous limiterons le nombre de paramètres de tuple à dix.
Lorsqu'un problème similaire se produit dans ma pratique, je regarde immédiatement en direction de Roslyn, je n'ai pas envie d'écrire beaucoup du même type de méthodes et de faire du copier-coller, il est plus facile de les générer.
Dans ce générique nous aidera. Il est nécessaire de générer 10 méthodes avec un nombre différent de génériques et de paramètres
La première approche du projectile était un peu différente, je voulais limiter les types de retour de lambdas (int, string, boolean, DateTime) et ne pas utiliser de types universels.
La difficulté est que même pour 3 paramètres, nous devrons générer 64 surcharges différentes, et lors de l'utilisation de génériques seulement 1:
IMappingExpression<TSource, TDest> To<TSource, TDest, TProjection,T,T1, T2, T3>( this MapperExpressionWrapper<TSource,TDest,TProjection> mapperExpressionWrapper, (Expression<Func<TDest, T>>, Expression<Func<TProjection, T>>) arg0, (Expression<Func<TDest, T1>>, Expression<Func<TProjection, T1>>) arg1, (Expression<Func<TDest, T2>>, Expression<Func<TProjection, T2>>) arg2, (Expression<Func<TDest, T3>>, Expression<Func<TProjection, T3>>) arg3) { ... }
Mais ce n'est pas le problème principal, nous générons le code, cela prend un certain temps et nous obtenons l'ensemble des méthodes nécessaires.
Le problème est différent, ReSharper ne ramassera pas autant de surcharges et refusera simplement de travailler, vous perdrez Intellisience et chargerez l'IDE.
Nous implémentons une méthode qui prend un tuple:
public static IMappingExpression<TSource, TDest> To <TSource, TDest, TProjection, T>(this MapperExpressionWrapper<TSource,TDest,TProjection> mapperExpressionWrapper, (Expression<Func<TDest, T>>, Expression<Func<TProjection, T>>) arg0) {
Tout d'abord, vérifions quelles propriétés le mappage peut être trouvé par convention, il s'agit d'une méthode assez simple, pour chaque propriété du DTO, nous recherchons le chemin dans l'entité d'origine. Les méthodes devront être appelées par réflexe, car vous devez obtenir un lambda typé, et son type dépend de prop.
Il est impossible d'enregistrer un lambda de type Expression <Func <TSource, object >>, puis AutoMapper mappera toutes les propriétés DTO au type object
private static void RegisterByConvention<TSource, TDest, TProjection>( MapperExpressionWrapper<TSource, TDest, TProjection> mapperExpressionWrapper) { var properties = typeof(TDest).GetProperties().ToList(); properties.ForEach(prop => {
RegisterRule reçoit un tuple qui définit les règles de mappage, il doit y être "connecté"
FromExpression et expression passées au tuple.
Cela nous aidera à l'expression.Invoquer, EF Core 2.0 ne le supportait pas, les versions ultérieures ont commencé à prendre en charge. Il vous permettra de réaliser une "composition lambd":
Expression<Func<Pupil,StudyGroup>> from = x=>x.EducationCard.StudyGroup; Expression<Func<StudyGroup,int>> @for = x=>x.Number;
Méthode RegisterRule :
private static void RegisterRule<TSource, TDest, TProjection, T (MapperExpressionWrapper<TSource,TDest,TProjection> mapperExpressionWrapper, (Expression<Func<TDest, T>>, Expression<Func<TProjection, T>>) rule) {
La méthode To est conçue pour être facile à étendre lors de l'ajout de paramètres de tuple. Lorsque vous ajoutez un autre tuple aux paramètres, vous devez ajouter un autre paramètre générique et appeler la méthode RegisterRule pour le nouveau paramètre.
Un exemple pour deux paramètres:
IMappingExpression<TSource, TDest> To<TSource, TDest, TProjection, T, T1> (this MapperExpressionWrapper<TSource,TDest,TProjection>mapperExpressionWrapper, (Expression<Func<TDest, T>>, Expression<Func<TProjection, T>>) arg0, (Expression<Func<TDest, T1>>, Expression<Func<TProjection, T1>>) arg1) { RegisterByConvention(mapperExpressionWrapper); RegisterRule(mapperExpressionWrapper, arg0); RegisterRule(mapperExpressionWrapper, arg1); return mapperExpressionWrapper.MappingExpression; }
Nous utilisons CSharpSyntaxRewriter , c'est un visiteur qui parcourt les nœuds de l'arbre de syntaxe. Nous prenons comme base une méthode avec To avec un argument et ajoutons un paramètre générique et appelons RegisterRule ;
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) {
ReWriteMethodInfo contient les nœuds d'arborescence de syntaxe générés que vous devez ajouter. Après cela, nous obtenons une liste de 10 objets de type MethodDeclarationSyntax (un arbre de syntaxe représentant une méthode).
Dans l'étape suivante, nous prenons la classe dans laquelle se trouve la méthode de modèle To et y écrivons toutes les nouvelles méthodes à l'aide d'un autre visiteur, dans lequel nous redéfinissons VisitClassDeclatation.
La méthode Update vous permet de modifier un nœud d'arbre existant, sous le capot itère sur tous les arguments passés et, si au moins un diffère de celui d'origine, crée un nouveau nœud.
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) {
En fin de compte, nous obtenons SyntaxNode - une classe avec des méthodes ajoutées, écrivez le nœud dans un nouveau fichier. Maintenant, nous avons surchargé la méthode To , qui prend de 1 à 10 tuples et un mappage beaucoup plus concis.
Point d'expansion
Regardons AutoMapper comme quelque chose de plus. Le fournisseur interrogeable ne peut pas analyser un grand nombre de requêtes, et une certaine partie de ces requêtes peut être réécrite différemment. C'est là qu'AutoMapper entre en jeu, l'extension est un point d'extension où nous pouvons ajouter nos propres règles.
Nous utiliserons le visiteur de l' article précédent qui remplace l'interpolation de chaîne par concaténation dans la méthode RegusterRule . Par conséquent, toutes les expressions qui définissent le mappage à partir de l'entité passeront par ce visiteur, éliminant ainsi la nécessité d'appeler à chaque fois ReWrite . Ce n'est pas une panacée, la seule façon dont nous pouvons à gérer est une projection, mais cela facilite quand même la vie.
Nous pouvons également ajouter une extension pratique, par exemple, pour le mappage par condition:
CreateMap<Passport,PassportDto>() .ToIf(x => x.Age, x => x < 18, x => $"{x.Age}", x => "Adult")
L'essentiel est de ne pas jouer avec cela et de ne pas commencer à transférer une logique complexe au niveau d'affichage
Github