How to provide additional parameter to Mapper's Map method? - c#

I'm trying to map one list to another, but not everything I want is in the source object. Let me explain:
These are my two objects:
public class SourceItem
{
public int Id { get; set;}
public string Name { get; set;}
}
public class TargetItem
{
public int TargetId { get; set;}
public string TargetName { get; set;}
public int Foo { get; set; }
}
This is how I configure my Mapper:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<SourceItem, TargetItem>()
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.Name));
}
}
When I do this...
var targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems);
...the targets list's objects will have Foo set to 0. Obviously. What I'm trying to do is to also initialize Foo (with the parameter). Like this:
fooParameter = 321;
List<TargetItem> targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems);
foreach(var target in targets)
{
target.Foo = fooParameter;
}
Is it possible, to do it using Mapper?
Thanks in advance!

You can't do exactly what you want, but you can get pretty close by specifying mapping options when you call Map.
fooParameter = 321;
var targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems, opt =>
opt.AfterMap(
(src, dest) => {
foreach (var d in dest)
{
d.Foo = fooParameter;
}
})
);

Related

Automapper: mapping between different types

Assuming I have a class structure like
public class Entity
{
public List<EntityChild> Children { get; set; }
}
public class EntityChild
{
public int Id { get; set; }
public string Name { get; set; }
}
and I want to map Entity using AutoMapper to a class EntityDto and reverse.
public class EntityDto
{
public List<int> EntityChildrenIds { get; set; }
}
I don't have any clue how to configure AutoMapper to map this properly in both directions. I know my Name property will be null when mapping from EntityDto to Entity but this would not be a problem.
For mapping both ways this configuration works for me:
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Entity, EntityDto>()
.ForMember(dest => dest.EntityChildrenIds, opt => opt.MapFrom(src => src.Children))
.ReverseMap();
cfg.CreateMap<EntityChild, int>().ConvertUsing(child => child.Id);
cfg.CreateMap<int, EntityChild>().ConvertUsing(id => new EntityChild
{
Id = id
});
});
Since the properties have different names we need to configure that mapping.
Then just add general mappings from EntityChild to int and back again and we're done.
if .ReverseMap(), as mentioned by #knoop, didn't work maybe you should map it manually:
CreateMap<Entity, EntityDto>(MemberList.None)
.ForMember(dest => dest.EntityChildrenIds, opts => opts.MapFrom(src => MapChildrenIds(src.Children)));
CreateMap<EntityDto, Entity>(MemberList.None)
.ForMember(dest => dest.Children, opts => opts.MapFrom(src => MapChildren(src.EntityChildrenIds)));
private List<EntityChild> MapChildren(List<int> entityChildrenIds)
{
var listEntityChild = new List<EntityChild>();
foreach (var childId in entityChildrenIds)
listEntityChild.Add(new EntityChild { Id = childId });
return listEntityChild;
}
private List<int> MapChildrenIds(List<EntityChild> children)
{
return children.Select(x => x.Id).ToList();
}

Automapper don't map null sources

I'm using an Automapper and I need to map a List of objects into a nested object. I have these objects:
public abstract class FooSrcBase
{
}
public class FooSrc : FooSrcBase
{
public bool Prop { get; set; }
}
public class FooDest
{
public bool Prop { get; set; }
}
public class FooDestGroup
{
public FooDest FooDest { get; set; }
}
public class Dest
{
public FooDestGroup FooDestGroup { get; set; }
}
I have IEnumerable<FooSrc> which contains FooSrc objects (there are many implementations and only one object per each type may exist in the source) and I need to map it into Dest object. I need this because of mapping into the view models for front end.
When I register mapping like this:
CreateMap<IEnumerable<FooSrc>, Dest>().ForPath(dest => dest.FooDestGroup.FooDest, opt => opt.MapFrom(src => src.FirstOrDefault(x => x.GetType() == typeof(FooSrc))));
CreateMap<FooSrc, FooDest>();
When I map empty list, a problem occurs in Dest object - FooDestGroup is an instance of object, which has a property FooDest with null value.
How it would be possible to make Dest property FooDestGroup map to null, if I provide empty list as a source?
Firstly, your abstract FooSrc class will need a different name (conflicts with your concrete class name FooSrc)
How about modifying the constructor on Dest to avoid the nesting issue?
Automapper is capable of mapping to the constructor parameter automatically, but if you need more advanced behaviour you can refer to https://docs.automapper.org/en/stable/Construction.html
Something like this should work:
public class Dest
{
public FooGroup FooGroup { get; set; }
public Dest(FooDest fooDest)
{
FooGroup = new FooGroup { FooDest = fooDest };
}
}
[..]
Mapper.CreateMap<FooSrc, Dest>();
Mapper.Map<List<Dest>>(listOfFooSrc);
This fixed my problem:
CreateMap<IEnumerable<FooSrcBase>, Dest>()
.ForMember(dest => dest.FooDestGroup, opt => opt.MapFrom(src => src.FirstOrDefault(x => x.GetType() == typeof(FooSrc))));
CreateMap<FooSrc, FooDestGroup>()
.ForMember(dest => dest.FooDest, opt => opt.MapFrom(src => src));
CreateMap<FooSrc, FooDest>();

AutoMapper: What is the difference between ForMember() and ForPath()?

I am reading AutoMapper's ReverseMap() and I can not understand the difference between ForMember() and ForPath(). Implementations was described here. In my experience I achieved with ForMember().
See the following code where I have configured reverse mapping:
public class Customer
{
public string Surname { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class CustomerDto
{
public string CustomerName { get; set; }
public int Age { get; set; }
}
static void Main(string[] args)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Customer, CustomerDto>()
.ForMember(dist => dist.CustomerName, opt => opt.MapFrom(src => $"{src.Surname} {src.Name}"))
.ReverseMap()
.ForMember(dist => dist.Surname, opt => opt.MapFrom(src => src.CustomerName.Split(' ')[0]))
.ForMember(dist => dist.Name, opt => opt.MapFrom(src => src.CustomerName.Split(' ')[1]));
});
// mapping Customer -> CustomerDto
//...
//
// mapping CustomerDto -> Customer
var customerDto = new CustomerDto
{
CustomerName = "Shakhabov Adam",
Age = 31
};
var newCustomer = Mapper.Map<CustomerDto, Customer>(customerDto);
}
It is working.
Question
Do ForMember and ForPath the same things or when should I use ForPath() over ForMember()?
In this case, to avoid inconsistencies, ForPath is translated internally to ForMember. Although what #IvanStoev says makes sense, another way to look at it is that ForPath is a subset of ForMember. Because you can do more things in ForMember. So when you have a member, use ForMember and when you have a path, use ForPath :)

"Store" objects during mapping with automapper

Let's say, we have two source types for mapping, and one destination:
class TSource
{
public float A { get; set; }
public float B { get; set; }
}
class TSupport
{
public float C { get; set; }
public float D { get; set; }
}
class TDestination
{
public float A { get; set; }
public float B { get; set; }
public float C { get; set; }
public float D { get; set; }
}
And instance of TSupport is obtained with some method, using TSource instance:
private TSupport GetSupportInstance(TSource source)
So, usually, I'd do this with double mapping:
Mapper.CreateMap<TSource, TDestination>();
Mapper.CreateMap<TSupport, TDestination>();
//...
var support = GetSupportInstance(source);
var destination = Mapper.Map<TSource, TDestination>(source);
Mapper.Map(support, destination);
This also may be done in single mapping using manual resolve:
Mapper.CreateMap<TSource, TDestination>()
.ForMember(desc => desc.C, opt => opt.ResolveUsing(src =>
{
var support = GetSupportInstance(src);
return support.C;
}))
.ForMember(desc => desc.D, opt => opt.ResolveUsing(src =>
{
var support = GetSupportInstance(src);
return support.D;
}));
As you can see, I'm using GetSupportInstance twice, for each property. So, finally, my question is - is it possible to obtain support in mapping once, store it somehow, and reuse it in mappings for different properties?
How about this:
Mapper.CreateMap<TSource, TDestination>()
.ForMember(d => d.C, x => x.Ignore())
.ForMember(d => d.D, x => x.Ignore())
.AfterMap((src, dst) => {
var support = GetSupportInstance(src);
dst.C = support.C;
dst.D = support.D;
});
I'd probably go about using a custom resolver that caches the instance you need. It would look something like this.
public class MySupportInstanceResolver : ValueResolver<TSource, TDestination>
{
private static readonly ISupportInstance _supportInstance;
[ImportingConstructor]
public MySupportInstanceResolver ()
{
_supportInstance=GetSupportInstance();
}
protected override string ResolveCore(Journal source)
{
return _supportInstance;
}
}
Then you could use it as:
cfg.CreateMap<TSource, Tdest>()
.ForMember(dest => dest.C,
opt => opt.ResolveUsing<MySupportInstanceResolver>().FromMember(name => name.C));
});

Mapping a List using a Custom Resolver with Automapper

I have 2 objects that I need to map to each other. They look like
public class Example1
{
CustomType1 Prop { get; set; }
List<CustomType1> List { get; set; }
}
public class Example2
{
Customtype2 Prop { get; set; }
List<Customtype2> List { get; set; }
}
public class CustomType1
{
public string SomeString { get; set; }
}
public class Customtype2
{
public string FirstPartOfSomeString { get; set; }
public string SecondPartOfSomeString { get; set; }
}
I want to make one CustomResolver that maps CustomType1 to CustomType2 and then use that resolver on the list. For example,
Mapper.CreateMap<Example1, Example2>()
.ForMember(d => d.Prop, opt => opt.ResolveUsing(myCustomResolver))
.ForMember(d => d.List, opt => opt.ResolveUsing( /*use myCustomResolver on a list here*/));
I have tried using something like:
Mapper.CreateMap<Example1, Example2>()
.ForMember(d => d.Prop, opt => opt.ResolveUsing(myCustomResolver))
.ForMember(d => d.List, opt => opt.MapFrom(s => s.List.Select(myCustomResolver.Resolve).ToList()));
but I seem to be missing something. Is there a way to do this with AutoMapper?
Have you tried adding a mapping between the custom types instead of using a resolver?
AutoMapper is intelligent enough to re-use mappings for lists...
Mapper.CreateMap<CustomType1, CustomType2>()
.ForMember(x => FirstPartOfSomeString, opts => opts.MapFrom(x => x.SomeString.Substring(5)))
.ForMember(x => SecondPartOfSomeString, opts => opts.MapFrom(x => x.SomeString.Substring(5, 5)));
Mapper.CreateMap<Example1, Example2>();

Categories

Resources