c# - formember - Automapper conversión personalizada de muchos a uno
automapper resolveusing (3)
Automapper muchos a una conversión
¿Cómo convertir los valores de muchas propiedades del objeto de origen a un solo tipo en el objeto de destino? ¿Puedo usar en este caso Value Resolvers ? O tal vez hay mejor solución?
Documentación
Aquí hay un ejemplo de la documentation : conversión uno a uno.
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Total,
opt => opt.ResolveUsing<CustomResolver>().FromMember(src => src.SubTotal));
Mapper.CreateMap<OtherSource, OtherDest>()
.ForMember(dest => dest.OtherTotal,
opt => opt.ResolveUsing<CustomResolver>().FromMember(src => src.OtherSubTotal));
public class CustomResolver : ValueResolver<decimal, decimal> {
// logic here
}
Caso
Quiero transferir dos objetos en uno ( muchos a una conversión ). Por ejemplo:
public class Document
{
public int CurrencyId {get; set;}
public int ExchangeRateId {get; set;}
}
public class DocumentDto
{
public Currency Currency {get; set;}
}
public class CurrencyDetails
{
public Currency Currency {get; private set;}
public ExchangeRate ExchangeRate {get; private set;}
public CurrencyDetails(Currency currency, ExchangeRate exchangeRate)
{
Currency = currency;
ExchangeRate = exchangeRate;
}
}
Me gustaría lograr algo así:
public class CurrencyResolver : ValueResolver<int, int, CurrencyDetails>
{
protected override Currency ResolveCore(int currencyId, int exchangeRateId)
{
var currency = new Currency(currencyId); //simplified logic
var exchangeRate = new ExchangeRate(exchangeRateId);
var currencyDetails = new CurrencyDetails(currency, exchangeRate);
return currencyDetails;
}
}
Sé que puedo pasar todo el objeto como el objeto de origen, pero para mí no es una solución:
ValueResolver<Document, Currency>
No puedo usar el objeto completo, porque tengo muchos tipos de documentos y no quiero crear un nuevo resolutor para cada documento. Ignorar el elemento (para conversión manual) tampoco está permitido en mi caso. La lógica de conversión de moneda debe ser realizada por AutoMapper.
Para mí es importante que la conversión se haya realizado en segundo plano (durante la conversión del tema principal).
Por ejemplo:
Document document;
var documentDto = Mapper.Map<DocumentDto>(document); // and in this moment i have proper CurrencyDetails object!
Gracias por tu consejo.
Mis soluciones
Pensé en dos soluciones, pero no me gustan (tan sucias)
Solución 1 : envuelva una clase con la interfaz:
public interface ICurrencyHolder
{
int CurrencyId {get; set;}
int ExchangeRateId {get; set;}
}
public class Document : ICurrencyHolder
{
public int CurrencyId {get; set;}
public int ExchangeRateId {get; set;}
}
y usar el resolvedor con los siguientes parámetros:
ValueResolver<ICurrencyHolder, Currency>
Solución 2 : tomar como tipo de objeto elemento de origen y tomar valores a través de la reflexión
ValueResolver<object, Currency>
¡Este es terrible!
Maybee puedes mapearlo así:
Mapper.CreateMap<Source, Destination>()
.ConstructUsing(s => Mapper.Map<Source, Currency>(s));
Mapper.CreateMap<Source, Currency>()
.ForMember(dst => dst.CurrencySymbol, map => map.MapFrom(src => src.DocumentDto.CurrencySymbol))
.ForMember(dst => dst.ExchangeRate , map => map.MapFrom(src => src.Document.ExchangeRate ));
También es posible:
Mapper.CreateMap<Source, Destination>()
.ConstructUsing(s => Mapper.Map<Source, Currency>(s));
Mapper.CreateMap<Source, Currency>()
.ConstructUsing(s => Mapper.Map<DocumentDto, Currency>(s))
.ConstructUsing(s => Mapper.Map<Document, Currency>(s));
Mapper.CreateMap<DocumentDto, Currency>();
Mapper.CreateMap<Document, Currency>();
Si comprendo correctamente, debe realizar la siguiente asignación: de ( CurrencyId
, ExchangeRateId
) a Currency
. Puede lograrlo usando Tuple
( es una clase estándar de .NET muy útil en estos casos):
Mapper.CreateMap<Tuple<int,int>, Currency>()
.ForMember(x => x.Currency, cfg => cfg.MapFrom(y => new Currency(y.Item1, y.Item2));
Invoque el asignador de la siguiente manera:
Mapper.Map<Tuple<int,int>, Currency>(Tuple.Create(doc.CurrencyId, doc.ExchangeRateId));
Si está seguro de que hará esto para cada tipo de documento:
Document document;
var documentDto = Mapper.Map<DocumentDto>(document);
Luego tendrás que definir mapeos para cada uno de ellos. Así que definitivamente me quedaría con la idea de ICurrencyHolder y usaría una resolución como esta:
El resolutor
public class CurrencyResolver : ValueResolver<ICurrencyHolder, Currency>
{
protected override Currency ResolveCore(ICurrencyHolder source)
{
return new Currency(source.CurrencyId, source.ExchangeRateId);
}
}
Documento "tipos"
public class Document : ICurrencyHolder
{
public int CurrencyId { get; set; }
public int ExchangeRateId { get; set; }
}
public class ExtendedDocument : ICurrencyHolder
{
public DateTime SomeDate { get; set; }
public int CurrencyId { get; set; }
public int ExchangeRateId { get; set; }
}
public interface ICurrencyHolder
{
int CurrencyId { get; set; }
int ExchangeRateId { get; set; }
}
Y los mapeos:
Mapper.CreateMap<Document, DocumentDto>().ForMember(m => m.Currency, opt => opt.ResolveUsing<CurrencyResolver>());
Mapper.CreateMap<ExtendedDocument, DocumentDto>().ForMember(m => m.Currency, opt => opt.ResolveUsing<CurrencyResolver>());
Con eso en su lugar, puede crear sus dto''s así y hacer que la Moneda se resuelva por sí misma en la etapa de asignación:
var dto = Mapper.Map<DocumentDto>(document);
var extendedDto = Mapper.Map<DocumentDto>(extendedDocument);