AutoMapper es una de las principales herramientas utilizadas en el desarrollo de aplicaciones empresariales, por lo que quiero escribir el menor código posible al definir el mapeo de entidades.
No me gusta la duplicación en MapFrom con proyecciones amplias.
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))
Me gustaría reescribirlo así:
CreateMap<Pupil, PupilDto>() .From(x=>x.IdentityCard.Passport).To()
Projectto
AutoMapper puede construir mapeo tanto en memoria como traducirlo a SQL, completa Expression, proyectando en DTO de acuerdo con las reglas que usted describió en los perfiles.
EntityQueryable.Select(dtoPupil => new PupilDto() { Name = dtoPupil.Identity.Passport, Surname = dtoPupil.Identity.Passport.Surname})
El 80% de la asignación que tengo que escribir es la asignación que completa Expression from IQueryble.
Esto es muy conveniente:
public ActionResult<IEnumerable<PupilDto>> GetAdultPupils(){ var result = _context.Pupils .Where(x=>x.Identity.Passport.Age >= 18 && ...) .ProjectTo<PupilDto>().ToList(); return result; }
En un estilo declarativo, formamos una consulta en la tabla Alumnos, agregamos filtros, proyectamos en el DTO deseado y lo devolvimos al cliente, para que pueda escribir todos los métodos de lectura de una interfaz CRUD simple. Y todo esto se hará a nivel de base de datos.
Es cierto que en aplicaciones serias es poco probable que tales acciones satisfagan a los clientes.
Contras AutoMapper'a
1) Es muy detallado, con el mapeo "ancho" tienes que escribir reglas que no encajan en una línea de código.
Los perfiles crecen y se convierten en archivos de código que se escriben una vez y cambian solo cuando se refactorizan los nombres.
2) Si usa el mapeo de acuerdo con la convención, el nombre se pierde
propiedades en DTO:
public class PupilDto {
3) Falta de seguridad tipo
1 y 2 son momentos desagradables, pero puede soportarlos, pero con la falta de seguridad de tipo cuando se registra ya es más difícil de soportar, esto no debe compilarse:
Queremos recibir información sobre dichos errores en la etapa de compilación, y no en tiempo de ejecución.
Usar envoltorios de extensión para eliminar estos puntos.
Escribir una envoltura
¿Por qué se debe escribir el registro de esta manera?
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))
Mucho más conciso:
CreateMap<Pupil,PupilDto>()
El método To aceptará tuplas si necesita especificar reglas de mapeo
IMapping <TSource, TDest> es la interfaz del fabricante de automóviles en la que se definen los métodos ForMember, ForAll () ... todos estos métodos devuelven esto (Api fluido).
Devolveremos el contenedor para recordar el método 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);
Ahora, el programador, después de haber escrito el método From, verá inmediatamente la sobrecarga del método To , por lo que le diremos la API, en tales casos podemos realizar todos los encantos de los métodos de extensión, ampliamos el comportamiento sin tener acceso de escritura a las fuentes de automapper
Tipificamos
Implementar un método mecanografiado es más complicado.
Tratemos de diseñar este método, necesitamos dividirlo en pedazos tanto como sea posible y eliminar toda la lógica en otros métodos. Acuerde inmediatamente que limitaremos el número de parámetros de tupla a diez.
Cuando ocurre un problema similar en mi práctica, inmediatamente miro en dirección a Roslyn, no tengo ganas de escribir muchos de los mismos métodos y hacer Copiar y Pegar, es más fácil generarlos.
En este genérico nos ayudará. Es necesario generar 10 métodos con un número diferente de genéricos y parámetros.
El primer enfoque para el proyectil fue un poco diferente, quería limitar los tipos de retorno de lambdas (int, string, boolean, DateTime) y no usar tipos universales.
La dificultad es que, incluso para 3 parámetros, tendremos que generar 64 sobrecargas diferentes, y cuando usemos genéricos solo 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) { ... }
Pero este no es el problema principal, generamos el código, tomará algún tiempo y obtendremos todo el conjunto de métodos necesarios.
El problema es diferente, ReSharper no detectará tantas sobrecargas y simplemente se negará a trabajar, perderá Inteligencia y cargará el IDE.
Implementamos un método que toma una tupla:
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) {
Primero, verifiquemos qué mapeo de propiedades se puede encontrar por convención, este es un método bastante simple, para cada propiedad en el DTO buscamos la ruta en la entidad original. Los métodos tendrán que llamarse reflexivamente, porque necesita obtener una lambda con tipo, y su tipo depende del accesorio.
Es imposible registrar una lambda de tipo Expression <Func <TSource, object >>, luego AutoMapper asignará todas las propiedades DTO para escribir objeto
private static void RegisterByConvention<TSource, TDest, TProjection>( MapperExpressionWrapper<TSource, TDest, TProjection> mapperExpressionWrapper) { var properties = typeof(TDest).GetProperties().ToList(); properties.ForEach(prop => {
RegisterRule recibe una tupla que define las reglas de mapeo, necesita estar "conectado" en ella
FromExpression y expresión pasaron a la tupla.
Esto nos ayudará a Expression. Invoque, EF Core 2.0 no lo admitió, las versiones posteriores comenzaron a admitirlo. Te permitirá hacer una "composición lambd":
Expression<Func<Pupil,StudyGroup>> from = x=>x.EducationCard.StudyGroup; Expression<Func<StudyGroup,int>> @for = x=>x.Number;
Método RegisterRule :
private static void RegisterRule<TSource, TDest, TProjection, T (MapperExpressionWrapper<TSource,TDest,TProjection> mapperExpressionWrapper, (Expression<Func<TDest, T>>, Expression<Func<TProjection, T>>) rule) {
El método To está diseñado para ser fácil de extender al agregar parámetros de tupla. Al agregar otra tupla a los parámetros, debe agregar otro parámetro genérico y llamar al método RegisterRule para el nuevo parámetro.
Un ejemplo para dos parámetros:
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; }
Utilizamos CSharpSyntaxRewriter , este es un visitante que recorre los nodos del árbol de sintaxis. Tomamos como base un método con To con un argumento y agregamos un parámetro genérico y llamamos a RegisterRule ;
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) {
ReWriteMethodInfo contiene los nodos del árbol de sintaxis generados que debe agregar. Después de eso, obtenemos una lista de 10 objetos de tipo MethodDeclarationSyntax (un árbol de sintaxis que representa un método).
En el siguiente paso, tomamos la clase en la que se encuentra el método de plantilla To y escribimos todos los métodos nuevos en él utilizando otro Visitor, en el que redefinimos VisitClassDeclatation.
El método Actualizar le permite editar un nodo de árbol existente, bajo el capó itera sobre todos los argumentos pasados, y si al menos uno difiere del original, crea un nuevo nodo.
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) {
Al final, obtenemos SyntaxNode, una clase con métodos agregados, escribimos el nodo en un nuevo archivo. Ahora hemos sobrecargado el método To , que toma de 1 a 10 tuplas y un mapeo mucho más conciso.
Punto de expansión
Veamos AutoMapper como algo más. Queryable Provider no puede analizar muchas consultas, y cierta parte de estas consultas puede reescribirse de manera diferente. Aquí es donde entra en juego AutoMapper, la extensión es un punto de extensión donde podemos agregar nuestras propias reglas.
Usaremos el visitante del artículo anterior que reemplaza la interpolación de cadenas con la concatenación en el método RegusterRule . Como resultado, todas las expresiones que definen el mapeo de la entidad pasarán por este visitante, eliminando así la necesidad de llamar a ReWrite cada vez. Esto no es una panacea, la única forma en que podemos administrar es una proyección, pero aún así facilita la vida.
También podemos agregar alguna extensión conveniente, por ejemplo, para mapeo por condición:
CreateMap<Passport,PassportDto>() .ToIf(x => x.Age, x => x < 18, x => $"{x.Age}", x => "Adult")
Lo principal es no jugar con esto y no comenzar a transferir la lógica compleja al nivel de visualización
Github