6

I am facing a challenge with AutoMapper between my source object and destination object. I will try to explain the sittuation. On my src object I have an string that according to its lenght it should be mapped to multiple properties of my destination object.

class source
{
   public int Id {get; set;}
   /* some other properties */
   public string Value {get; set;}
}

class destination
{
   public int Id {get; set;}
   /* some other properties with the same name as the source */
   public string Value1 {get; set;}
   public string Value2 {get; set;}
   public string Value3 {get; set;}
}

The max length expected is 30 chars (It can be less than that which will be mapped to only two properties or one). So every 10 will be mapped to each destination property. I was trying to use the ResolveUsing method from AutoMapper but there is no way to let the function to know which segment I should bring back. So I was thinking to ignore the mapping of this properties and do this manually after Automapper has dones its job with other properties

JAVH
  • 155
  • 1
  • 3
  • 13

2 Answers2

12

On way you could do this is to use .ConstructUsing to tell AutoMapper how to create the object You could create a function that maps Value1, Value2, Value3 manually, then letting AutoMapper map the remaining properties. For example:

static destination ConstructDestination(source src)
{
    List<string> chunked = src.Value
        .Select((ch, index) => new { Character = ch, Index = index })
        .GroupBy(
            grp => grp.Index / 10,
            (key, grp) => new string(grp.Select(itm => itm.Character).ToArray()))
        .ToList();

    var dest = new destination
    {
        Value1 = chunked.Count > 0 ? chunked[0] : null,
        Value2 = chunked.Count > 1 ? chunked[1] : null,
        Value3 = chunked.Count > 2 ? chunked[2] : null
    };

    return dest;
}

Mapper.CreateMap<source, destination>()
    .ConstructUsing(ConstructDestination)
    .ForMember(dest => dest.Value1, opt => opt.Ignore())
    .ForMember(dest => dest.Value2, opt => opt.Ignore())
    .ForMember(dest => dest.Value3, opt => opt.Ignore());
    /* Id is mapped automatically. */

Of course if in your actual scenario you have more than three Value fields, this could become nasty--in that case you could use reflection to set the properties.

Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
  • Thanks for your response. I did not have any knowledge on how to use the ConstructUsing Now it is clear. – JAVH Nov 13 '14 at 22:24
7

You can create a mapping function using the ForMember specification

I.e.

private void CreateMap(){
    Mapper.CreateMap<source,destination>()
        .ForMember(v=> v.Value1,
            opts => opts.MapFrom( src=> src.Value.Substring(0,10)))
        .ForMember(v=> v.Value2,
            opts => opts.MapFrom( src=> src.Value.Substring(10,10)))
        .ForMember(v=> v.Value3,
            opts => opts.MapFrom( src=> src.Value.Substring(20,10)));
}

You can just evaluate on every mapping that the original string contains the appropiate lenght and otherwise return string.Empty or stuff it to suit your needs.

Usage(using LinqPad):

void Main(){
   CreateMap();
   var source = new source();
   source.Id=1;
   source.Value="123454678901234546789012345467";
   var res = Mapper.Map<destination>(source);
   res.Dump();
}
Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
soulus
  • 81
  • 3
  • This almost works--the problem is that you'd have to have two length checks for every `MapFrom` call--one for the start and end index of the string to make sure the `Substring` call doesn't fail. – Andrew Whitaker Nov 13 '14 at 20:22
  • So true! I would upvote yours, but I don't have enough rep yet =P – soulus Nov 13 '14 at 21:17
  • 1
    @soulus Actually I prefer your solution because it would work also when you map to an existing instance (i.e. when you load an entity from the database using EF, object creation and mapping have to be two separate operations) – Leonardo Spina Feb 22 '18 at 12:45