Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automapper custom many-to-one conversion

Automapper Many To One conversion

How to convert values of many properties from the source object to a single type in destination object? Can I use in this case Value Resolvers? Or maybe there is better solution?

Documentation

Here is example from documentation - one to one conversion

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
}

Case

I want to transfer two objects into one (many to one conversion). For example:

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

I would like to achieve something like that:

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

I know that I can pass the whole object as the source object, but for me it is not a solution:

ValueResolver<Document, Currency>

I can't use full object, because I have many document types and i don't want to create new resolver for each document. Ignoring the element (for manual conversion) is also not allowed in my case. Currency conversion logic must be conducted by AutoMapper.

It's important for me that the conversion took place in background (during the conversion of the main subject).

For example:

Document document;
var documentDto = Mapper.Map<DocumentDto>(document); // and in this moment i have proper CurrencyDetails object!

Thank you for your advice.

My solutions

I figured two solutions, but I dont like them (soooo dirty)

Solution 1 - wrap a class with interface:

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

and use resolver with following parameters:

ValueResolver<ICurrencyHolder, Currency>

Solution 2 - take as source element object type and take values via reflection

ValueResolver<object, Currency>

This is terrible!

like image 794
Pawel Maga Avatar asked May 18 '15 10:05

Pawel Maga


2 Answers

If I understand correctly, you need to do the following mapping: from (CurrencyId, ExchangeRateId) to Currency. You can achieve it using Tuple (it is a standard .Net class very handy in these cases):

Mapper.CreateMap<Tuple<int,int>, Currency>()
   .ForMember(x => x.Currency, cfg => cfg.MapFrom(y => new Currency(y.Item1, y.Item2));

Invoke the mapper as follows:

Mapper.Map<Tuple<int,int>, Currency>(Tuple.Create(doc.CurrencyId, doc.ExchangeRateId));
like image 148
Joanvo Avatar answered Oct 16 '22 11:10

Joanvo


Maybee you can map it like this:

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

Also possible:

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>();
like image 34
Guy Levin Avatar answered Oct 16 '22 10:10

Guy Levin