Automapper - how to map two complex collections to one destination collection object - c#

I have the two distinct objects and would like to map them to one destination object. The source objects are complex objects which contain multiple child objects which would also need to be mapped. I've tried something similar to the below example but as expected, the last mapping will overwrite any previous mapping.
CreateMap<sourceObject, destinationObject>()
.ForMember(d => d.Addresses, o => o.MapFrom(s => s.Addresses.MainAddresses))
.ForMember(d => d.Addresses, o => o.MapFrom(s => s.Addresses.SomeOtherAddresses))
I suppose I'm looking for something like a MapFrom().AndThenMapFrom() method (a join or union type transformation).
I have used a custom value resolver to get around the issue but this seems to defeat the purpose of automapper in my eyes. Any other suggestions would be welcomed.

If you want to concatenate in Addresses results of mappings (MainAddresses to Addresses and SomeOtherAddresses to Addresses), one solution would be
CreateMap<sourceObject, destinationObject>()
.ForMember(
d => d.Addresses,
o => o.MapFrom(
s => s.Addresses.MainAddresses
.Cast<object>()
.Concat(s.Addresses.SomeOtherAddresses)))
or
CreateMap<sourceObject, destinationObject>()
.ForMember(
d => d.Addresses,
o => o.MapFrom(
s => s.Addresses.MainAddresses
.Cast<IAddress>()
.Concat(s.Addresses.SomeOtherAddresses)))
if objects in MainAddresses and SomeOtherAddresses realize IAddress interface.
Another solution is to do it in Before/AfterMap method
CreateMap<sourceObject, destinationObject>()
.BeforeMap(
(s, d, c) =>
{
var mainAddresses = c.Mapper.Map<IEnumerable<Address>>(s.Addresses.MainAddresses);
var someOtherAddresses = c.Mapper.Map<IEnumerable<Address>>(s.Addresses.SomeOtherAddresses);
d.Addresses = mainAddresses
.Concat(someOtherAddresses)
.ToArray();
})
.ForMember(d => d.Addresses, o => o.Ignore());
In this case mapping for Addresses should be ignored, because we do it "manually" in BeforeMap. Unfortunately both solutions are not elegant as most of simple automapper rules.

Related

How can I pass a constructor parameter to an implicit Dictionary mapping using AutoMapper?

Let's say I have a TestClass to TestClassModel mapping.
Without any other mappings declared, I can already map a Dictionary<string,TestClass> to a Dictionary<string,TestClassModel>.
Imagine though that the Dictionary<string,TestClass> had been initialized using new Dictionary<string,TestClass>(StringComparer.OrdinalIgnoreCase). This allows case-insensitive lookups.
How can the StringComparer.OrdinalIgnoreCase constructor parameter be preserved in the mapped Dictionary? I've tried an explicit Dictionary->Dictionary mapping with .ForCtorParam, but that just results in an empty destination Dictionary (as you would then have to add KeyValuePair mappings.)
Fiddle: https://dotnetfiddle.net/KYQNWT
I did get it working, but not sure why it doesn't work on DotNetFiddle. If I run it in my own app it works just fine. I left in other things I tested for.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<TestClass, TestClassModel>()
.ForMember(d => d._F1, s => s.MapFrom(o => o.F1));
cfg.CreateMap<KeyValuePair<string, TestClass>, KeyValuePair<string, TestClassModel>>()
.ConstructUsing(x => new KeyValuePair<string, TestClassModel>(x.Key, Mapper.Map<TestClassModel>(x.Value)));
cfg.CreateMap(typeof(Dictionary<,>), typeof(Dictionary<string, TestClassModel>))
//.ForCtorParam("collection", opt => opt.MapFrom(src => Mapper.Map<IEnumerable<KeyValuePair<string, TestClassModel>>>(src)))
//.ForCtorParam("comparer", opt => opt.MapFrom(src => StringComparer.OrdinalIgnoreCase));
.ConstructUsing(src => new Dictionary<string, TestClassModel>(Mapper.Map<IEnumerable<KeyValuePair<string, TestClassModel>>>(src), StringComparer.OrdinalIgnoreCase));
});
If you're going to run this in any type of real environment I highly recommend using the instanced type of automapper.

Automapper.mapper.map doesn't work but mapper.dynamicmap work

First of all, sorry for my bad english! I'm french.
I tried to find on google but without any result so, this is my automapper configuration :
public void CreateMap(IMapperConfiguration cfg)
{
cfg.CreateMap<GroupeApplication, GroupeApplicationContract>();
cfg.CreateMap<Application, ApplicationContract>()
.ForMember(d => d.GroupeApplicationId, o => o.MapFrom(s => s.GroupeApplication != null ? s.GroupeApplication.Id : Guid.Empty));
cfg.CreateMap<Application, ApplicationWithAscendantContract>()
.ForMember(d => d.GroupeApplication, o => o.MapFrom(s => Mapper.Map<GroupeApplicationContract>(s.GroupeApplication)));
cfg.CreateMap<GroupeApplication, GroupeApplicationWithDescendantContract>()
.ForMember(d => d.Applications, o => o.MapFrom(s => Mapper.Map<List<ApplicationContract>>(s.Applications)));
Mapper.AssertConfigurationIsValid();
}
In fact, groupeapplication has list of application, and Mapper.Map failed when it try to map my applications collection... this is the error message :
Missing type map configuration or unsupported mapping.
Mapping types:
Application -> ApplicationContract
UGO.Distribution.Domain.Application -> UGO.Distribution.Shared.Contracts.ApplicationContract
Destination path:
List`1[0]
Source value:
UGO.Distribution.Domain.Application
If I replace the last config with this it works (I replace Mapper.Map by Mapper.DynamicMap) :
cfg.CreateMap<GroupeApplication, GroupeApplicationWithDescendantContract>()
.ForMember(d => d.Applications, o => o.MapFrom(s => Mapper.DynamicMap<List<ApplicationContract>>(s.Applications)));
I could be satisfied, but that is not for me is that this method is deprecated in automapper 4.0.3 !!
Is there any explanation or a better solution for this problem?
Instead of putting ForMember(...), try:
ForMember<ObjectThatYouWantMap>(...);

Automapper merge/combine mappings

Let's assume I have these mappings:
var mapping1 = Mapper.CreateMap<Order, BaseDto>()
.ForMember(o => o.Referrer, m => m.Ignore());
var mapping2 = Mapper.CreateMap<Order, DetailDto>()
.ForMember(o => o.Price, m => m.Ignore());
var mapping3 = Mapper.CreateMap<Order, UpdateDto>()
.ForMember(o => o.Temp, m => m.Ignore());
Inheritance of Dtos:
DetailDto : BaseDto
UpdateDto : BaseDto
Is there any way to merge/combine the first mapping into mapping2 and mapping3?
Something like this:
var mapping1 = Mapper.CreateMap<Order, BaseDto>()
.ForMember(o => o.Referrer, m => m.Ignore());
var mapping2 = Mapper.CreateMap<Order, DetailDto>()
.Import(mapping1);
.ForMember(o => o.Price, m => m.Ignore());
var mapping3 = Mapper.CreateMap<Order, UpdateDto>()
.Import(mapping1);
.ForMember(o => o.Temp, m => m.Ignore());
The .Include function does not seem to do what I want as the following doesn't work:
var mapping1 = Mapper.CreateMap<Order, BaseDto>()
.Include<Order, DetailDto>()
.Include<Order, UpdateDto>()
.ForMember(o => o.Referrer, m => m.Ignore());
var mapping2 = Mapper.CreateMap<Order, DetailDto>()
.ForMember(o => o.Price, m => m.Ignore());
var mapping3 = Mapper.CreateMap<Order, UpdateDto>()
.ForMember(o => o.Temp, m => m.Ignore());
Update: Edited question to be more specific
I wonder if its a bug. The documentation states:
In AutoMapper 2.0, this becomes:
Mapper.CreateMap<Order, OrderDto>()
.Include<OnlineOrder, OnlineOrderDto>()
.Include<MailOrder, MailOrderDto>()
.ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
Mapper.CreateMap<OnlineOrder, OnlineOrderDto>();
Mapper.CreateMap<MailOrder, MailOrderDto>();
Because we have defined the mapping for the base class, we no longer have to define it in the child mappings.
I have just upgraded to AutoMapper 2.2.1-ci9000 and I'm getting the same behaviour as you (ie, Referrer is not being ignored).
I suggest you create an issue for it and see what they say.
I realise this comes a bit late, but for the benefit of someone else with similar issues...
I struggled with the same problem until I found this page on the AutoMapper Github wiki.
In summary:
Inherited configuration is evaluated against priorities (to resolve issues where multiple maps have conflicting configuration for the same members). An Ignore() that is inherited has a lower priority than AutoMapper's standard convention-based mapping. So where the source and destination types both have a Referrer property, the convention-based mapping for the most derived types overrides the inherited Ignore().
That suggests then, that in this instance, you need to repeat the Ignore() for the maps for each type derived from BastDto

How to ignore all destination members, except the ones that are mapped? [duplicate]

This question already has answers here:
AutoMapper: "Ignore the rest"?
(20 answers)
Closed 6 years ago.
Is there a way to do this? We have a SummaryDto that maps from three different types, and when we create a map for each type, props that are not mapped are throwing an error. There are about 35 attributes on the summary dto. To use Ignore() option on each one is just too much trouble. Is there a global ignore? Something like
CreateMap<Source,Target>()
.IgnoreAllUnmapped();
This is working for me:
public static class MappingExpressionExtensions
{
public static IMappingExpression<TSource, TDest> IgnoreAllUnmapped<TSource, TDest>(this IMappingExpression<TSource, TDest> expression)
{
expression.ForAllMembers(opt => opt.Ignore());
return expression;
}
}
Because ForAllMembers returns void, calling ForAllMembers(o => o.Ignore()) without this extension method wouldn't work. We want to keep the mapping expression available to enable the subsequent mappings:
CreateMap<Source, Destination>()
.IgnoreAllUnmapped()
.ForMember(d => d.Text, o => o.MapFrom(s => s.Name))
.ForMember(d => d.Value, o => o.MapFrom(s => s.Id));
I struggled with this one for quite a while too, or at least a problem similar to this. I had a class with a lot of properties on it (about 30) and I only wanted to map about 4 of them. It seems crazy to add 26 ignore statements (especially when it means that future changes to the class will mean having to update them!)
I finally found that I could tell AutoMapper to ignore everything, and then explicitly add the ones that I did want.
// Create a map, store a reference to it in a local variable
var map = CreateMap<Source,Target>();
// Ignore all members
map.ForAllMembers(opt => opt.Ignore());
// Add mapping for P1
map.ForMember(dest => dest.P1, opt => opt.MapFrom( src => src.P1));
// Add other mappings...
map.ForMember(dest => dest.P2, opt => opt.MapFrom( src => src.P2));
map.ForMember(dest => dest.P3, opt => opt.MapFrom( src => src.P3));
map.ForMember(dest => dest.P4, opt => opt.MapFrom( src => src.P4));
You'd be forgiven for thinking that you could just do this (but don't because it wont compile):
// This won't compile
CreateMap<Source,Target>()
.ForAllMembers(opt => opt.Ignore())
.ForMember(dest => dest.P1, opt => opt.MapFrom( src => src.P1));
The reason why this doesn't work is that the ForAllMembers() method doesn't support the fluent style of chaining (at least in the current version 2.0).
The good news is that the non-chaining version does indeed work. The one caveat of course is that you need to explicitly tell AutoMapper which members to map. I haven't yet found an easy way to have it both ways so that you can still use the implied mappings and ignore the broken ones.
To avoid having to explicitly specify the mapped properties, you can use IgnoreAllNonExisting. It ignores any destination properties that don't have mapped source properties.
Try to use .ConvertUsing() in your case, e.g.
CreateMap<Source,Target>()
.ConvertUsing(converter=> new Target(){
P1 = converter.P1,
....
});
So, you can describe all properties what you want to have in your object, other will be ignored.
Extension method which allows fluent syntax for the ForAllMembers method:
public static IMappingExpression<TSource, TDestination> IgnoreAllMembers<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression
)
{
expression.ForAllMembers(opt => opt.Ignore());
return expression;
}
Usage:
The call to IgnoreAllMembers must be before the call to ForMember.
CreateMap<LocationRevision, Dto.LocationAddressMap>()
.IgnoreAllMembers()
.ForMember(m => m.LocationId, opt => opt.MapFrom(src => src.Id))
;

Using Automapper (.net c#) to map to a variable not in Src for use in linq2sql classes?

I have been using automapper pretty successfully lately but i have come across a small problem for mapping the Dest to a variable not a available in the Src.... An example explains it better.. basically i am mapping from dest to src as per instructions .. all working well but i need to now map a destination to a variable named reservationNumber which is local variable not part of ORDER ... anyone know how to do this??
I am using automapper to map from order to reservation for use in linq2sql as Reservation is my linq2sql class.
Is the small example, i would appreciate any input.
string reservationNumber = "1234567890"; // this is the local variable.. It will be dynamic in future..
Mapper.CreateMap<Order, Reservation>()
.ForMember(dest => dest.ReservationNumber, reservationNumber // THIS OBVIOUSLY FAILS)
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.name))
.ForMember(dest => dest.Surname1, opt => opt.MapFrom(src => src.surname1))
.ForMember(dest => dest.Surname2, opt => opt.MapFrom(src => src.surname2))
.ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.email))
.ForMember(dest => dest.Telephone, opt => opt.MapFrom(src => src.telephone))
;
// Perform mapping
Reservation reservation = Mapper.Map<Order, Reservation>(order);
Try this:
Mapper.CreateMap<Order, Reservation>()
.ForMember(dest => dest.ReservationNumber, opt => opt.MapFrom(src => reservationNumber));
That MapFrom option takes any Func. Your other options would be to map to an existing destination object, with the reservation number already there. Or, use a custom value resolver (ResolveUsing), if you need to get the reservation number using a custom service or something.
The CreateMap call only needs to happen once per AppDomain, so you may want to check the other two options and see if they fit your needs.

Categories