Digamos que tengo dos clases modelo:
public class People {
public string FirstName {get;set;}
public string LastName {get;set;}
}
También tenga un teléfono de clase:
public class Phone {
public string Number {get;set;}
}
Y quiero convertirme a un PeoplePhoneD de esta manera:
public class PeoplePhoneDto {
public string FirstName {get;set;}
public string LastName {get;set;}
public string PhoneNumber {get;set;}
}
Digamos que en mi controlador tengo:
var people = repository.GetPeople(1);
var phone = repository.GetPhone(4);
// normally, without automapper I would made
return new PeoplePhoneDto(people, phone) ;
Parece que no puedo encontrar ningún ejemplo para este escenario. Es posible ?
Nota: El ejemplo no es real, solo para esta pregunta.
c#
automapper
Bart Calixto
fuente
fuente
PeoplePhoneDto
tener un miembroPeople
yPhone
?Respuestas:
No puede asignar directamente muchas fuentes a un solo destino; debe aplicar los mapas uno por uno, como se describe en la respuesta de Andrew Whitaker . Entonces, debes definir todas las asignaciones:
Luego cree el objeto de destino mediante cualquiera de estas asignaciones y aplique otras asignaciones al objeto creado. Y este paso se puede simplificar con un método de extensión muy simple:
public static TDestination Map<TSource, TDestination>( this TDestination destination, TSource source) { return Mapper.Map(source, destination); }
El uso es muy simple:
var dto = Mapper.Map<PeoplePhoneDto>(people) .Map(phone);
fuente
Map
método de extensión sea visible en el punto en que realiza el mapeo (es decir, se agrega la directiva de uso correcto)Podrías usar un
Tuple
para esto:En caso de que tenga más modelos fuente, puede usar una representación diferente (Lista, Diccionario o algo más) que reunirá todos estos modelos como fuente.
El código anterior debe colocarse preferiblemente en algún archivo de configuración de AutoMapper, establecerse una vez y de forma global y luego usarse cuando corresponda.
AutoMapper de forma predeterminada admite solo una fuente de datos. Entonces, no hay posibilidad de configurar directamente múltiples fuentes (sin envolverlo en una colección) porque entonces, ¿cómo sabríamos qué en caso de que, por ejemplo, dos modelos de fuente tengan propiedades con los mismos nombres?
Aunque hay alguna solución para lograr esto:
public static class EntityMapper { public static T Map<T>(params object[] sources) where T : class { if (!sources.Any()) { return default(T); } var initialSource = sources[0]; var mappingResult = Map<T>(initialSource); // Now map the remaining source objects if (sources.Count() > 1) { Map(mappingResult, sources.Skip(1).ToArray()); } return mappingResult; } private static void Map(object destination, params object[] sources) { if (!sources.Any()) { return; } var destinationType = destination.GetType(); foreach (var source in sources) { var sourceType = source.GetType(); Mapper.Map(source, destination, sourceType, destinationType); } } private static T Map<T>(object source) where T : class { var destinationType = typeof(T); var sourceType = source.GetType(); var mappingResult = Mapper.Map(source, sourceType, destinationType); return mappingResult as T; } }
Y entonces:
var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>(people, phone);
Pero para ser bastante honesto, a pesar de que estoy usando AutoMapper desde hace algunos años, nunca he tenido la necesidad de usar mapas de múltiples fuentes. En los casos en los que, por ejemplo, necesitaba varios modelos de negocio en mi modelo de vista única, simplemente incrustaba estos modelos dentro de la clase de modelo de vista.
Entonces, en su caso, se vería así:
public class PeoplePhoneDto { public People People { get; set; } public Phone Phone { get; set; } }
fuente
PeoplePhoneDto
que sugirió se ve bien, pero sigo pensando que el mapeo de múltiples fuentes es útil, sobre todo en modelos de vista de mapeo. Creo que la mayoría de los escenarios del mundo real requieren múltiples fuentes para construir un modelo de vista. Supongo que podría crear modelos de vista que no se aplanan para solucionar el problema, pero creo que es una buena idea crear los modelos de vista sin importar cómo se ve el esquema comercial.Tuple<People, Phone>
lo mismo queTuple<Phone, People>
?Tuple
expone el primer argumento de tipo comoItem1
, el segundo comoItem2
, etc. En ese sentido, el orden importa.Escribiría un método de extensión de la siguiente manera:
public static TDestination Map<TSource1, TSource2, TDestination>( this IMapper mapper, TSource1 source1, TSource2 source2) { var destination = mapper.Map<TSource1, TDestination>(source1); return mapper.Map(source2, destination); }
Entonces el uso sería:
fuente
tal vez parezca una publicación antigua, pero puede haber algunos tipos que todavía luchan con el mismo problema, refiriéndose a la documentación de la función AutoMapper IMapper Map , podemos reutilizar el mismo objeto de destino existente para el mapeo de una nueva fuente, siempre que ya haya creado un mapa para cada origen a destino en el perfil, entonces puede usar este método de extensión simple:
public static class MappingExtentions { public static TDestination Map<TDestination>(this IMapper mapper, params object[] sources) where TDestination : new() { return Map(mapper, new TDestination(), sources); } public static TDestination Map<TDestination>(this IMapper mapper, TDestination destination, params object[] sources) where TDestination : new() { if (!sources.Any()) return destination; foreach (var src in sources) destination = mapper.Map(src, destination); return destination; } }
tenga en cuenta que he creado una restricción para el tipo de destino que dice que debe ser un tipo capaz de crear instancias. si tu tipo no es así, usa en
default(TDestination)
lugar denew TDestination()
.Advertencia : este tipo de mapeo es un poco peligroso a veces porque las propiedades del mapeo de destino pueden ser sobrescritas por múltiples fuentes y rastrear el problema en aplicaciones más grandes puede ser un dolor de cabeza, hay una solución suelta que puede aplicar, puede hacer lo siguiente, pero de nuevo, no es una solución sólida en absoluto:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string PhoneNumber { get; set; } } public class Contact { public string Address { get; set; } public string PhoneNumber { get; set; } public string Other{ get; set; } } public class PersonContact { public string FirstName { get; set; } public string LastName { get; set; } public string Address{ get; set; } public string PhoneNumber { get; set; } } public class PersonMappingProfile : MappingProfile { public PersonMappingProfile() { this.CreateMap<Person, PersonContact>(); this.CreateMap<Phone, PersonContact>() .ForMember(dest => dest.PhoneNumber, opt => opt.MapFrom((src, dest) => dest.PhoneNumber ?? src.PhoneNumber)) // apply mapping from source only if the phone number is not already there, this way you prevent overwriting already initialized props .ForAllOtherMembers(o => o.Ignore()); } }
fuente
Si tiene un escenario en el que el tipo de destino debe mapearse desde una de las fuentes y desea utilizar proyecciones de linq, puede hacer lo siguiente.
Necesitaba esto principalmente para consultas de aplicación cruzada como esta.
var dbQuery = from p in _context.People from ph in _context.Phones .Where(x => ...).Take(1) select ValueTuple.Create(p, ph); var list = await dbQuery .ProjectTo<PeoplePhoneDto>(_mapper.ConfigurationProvider) .ToListAsync();
fuente
Ya se ofrecen muchas opciones, pero ninguna se ajusta realmente a lo que quería. Me estaba quedando dormido anoche y tuve el pensamiento:
Digamos que desea asignar sus dos clases,
People
yPhone
paraPeoplePhoneDto
public class People { public string FirstName {get;set;} public string LastName {get;set;} }
+
public class Phone { public string Number {get;set;} }
=
public class PeoplePhoneDto { public string FirstName {get;set;} public string LastName {get;set;} public string PhoneNumber {get;set;} }
Todo lo que realmente necesita es otra clase contenedora para propósitos de Automapper.
public class PeoplePhone { public People People {get;set;} public Phone Phone {get;set;} }
Y luego defina el mapeo:
Y usarlo
var dto = Map<PeoplePhoneDto>(new PeoplePhone { People = people, Phone = phone, });
fuente