Mapping a List using a Custom Resolver with Automapper - c#

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

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 - How map complex objects to simpler model

I want map Origin.CityId and Origin.StateId properties of
Itinerary class to OriginCityId and OriginStateId properties
of ItineraryModel class.
Ex: Itinerary itinerary = Mapper.Map<Itinerary>(ItineraryModel);
My ViewModel
public class ItineraryModel : BaseModel
{
public int OriginCityId { get; set; }
public int OriginStateId { get; set; }
public bool Published { get; set; }
}
My Entity
public class Itinerary : BaseEntity
{
public City Origin { get; set; }
public bool Published { get; set; }
}
My mapping that tried do
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<ItineraryModel, Itinerary>()
.ForPath(x => x.Origin.CityId, opt => opt.MapFrom(src => src.OriginCityId))
.ForPath(x => x.Origin.StateId, opt => opt.MapFrom(src => src.OriginStateId))
.ReverseMap();
}
}
I would like to too .ReverseMap() but can't find right syntax.
You need to add two mappings for mapping to Itenerary
CreateMap<ItineraryModel, City>()
.ForMember(city => city.CityId, expression => expression.MapFrom(itineraryModel => itineraryModel.OriginCityId))
.ForMember(city => city.StateId, expression => expression.MapFrom(itineraryModel => itineraryModel.OriginStateId));
CreateMap<ItineraryModel, Itinerary>()
.ForMember(itinerary => itinerary.Origin, expression => expression.MapFrom(itineraryModel => itineraryModel));
Similarly you can define reverse mappings manually if needed.
BTW ReverseMap() is not recommended by author
https://jimmybogard.com/automapper-usage-guidelines/

AutoMapper Conditional Entity Mapping

I have a db entity which stores the Order Addresses like this...
And I have the BLL classes like this...
public class DeliveryAddress
{
public string Id { get; set; }
public string PersonyName { get; set; }
public string CompanyName { get; set; }
public List<string> AddressLines { get; set; }
public string Zip { get; set; }
public string City { get; set; }
public string CountryCode { get; set; }
}
and another class like this...
public class InvoiceAddress
{
public string Id { get; set; }
public string PersonyName { get; set; }
public string CompanyName { get; set; }
public List<string> AddressLines { get; set; }
public string Zip { get; set; }
public string City { get; set; }
public string CountryCode { get; set; }
}
and I want to map the EF entity to the above classes on the basis of AddressType column. Can anybody explain me how to do that ?
UPDATE
I want to map to OR.DeliveryAddress if the addressType is "Delivery" and to OR.InvoiceAddress if the addressType is "Invoice"
So far, I have been able to do this, but I don't know how to apply condition on the entity mapping level...
Mapper.CreateMap<OrderAddress, OR.DeliveryAddress>()
.ForMember(d => d.City, o => o.MapFrom(s => s.city))
.ForMember(d => d.CompanyName, o => o.UseValue(string.Empty))
.ForMember(d => d.CountryCode, o => o.MapFrom(s => s.countryCode))
.ForMember(d => d.Id, o => o.MapFrom(s => s.id))
.ForMember(d => d.PersonyName, o => o.MapFrom(s => s.name))
.ForMember(d => d.Zip, o => o.MapFrom(s => s.zip));
UPDATE 2
After discussion with #Yuliam Here is the Fiddle that I could come up with for my problem...
You can create a customer mapper to object. And also you don't have to specify each property using ForMember because if the difference is only upper case / lower case (unless for PersonName), by default AutoMapper is case insensitive when mapping the property name.
Create a custom mapper to object.
public class AddressConverter : ITypeConverter<OrderAddress, object>
{
public object Convert(ResolutionContext context)
{
var o = context.SourceValue as OrderAddress;
if (o == null) return null;
if (o.addressType == "Delivery") return Mapper.Map<OR.DeliveryAddress>(o);
if (o.addressType == "Invoice") return Mapper.Map<OR.InvoiceAddress>(o);
return null;
}
}
Then define the mapper.
Mapper.CreateMap<OrderAddress, OR.DeliveryAddress>()
.ForMember(d => d.PersonyName, o => o.MapFrom(s => s.name));
Mapper.CreateMap<OrderAddress, OR.InvoiceAddress>()
.ForMember(d => d.PersonyName, o => o.MapFrom(s => s.name));
Mapper.CreateMap<OrderAddress, object>().ConvertUsing<AddressConverter>();
Usage.
var orderAddressDto = Mapper.Map<object>(orderAddress);
The actual orderAddressDto type will be based on the addressType. If you have an interface or base class for OR.DeliveryAddress and OR.InvoiceAddress that would be more strongly type. Then replace the object type with the interface / base class.
You may want to try taking a look at ResolveUsing
Semi-pseudo code, as I don't know what your entire Domain Model looks like:
Mapper.CreateMap<OrderObject, OrderDto>()
.ForMember(x => x.Address, opt => opt.ResolveUsing(oo => oo.Type == Invoice ? oo.InvoiceAddress : oo.DeliveryAddress));
I'm assuming here that you have an actual Order entity, which you're trying to make to an 'OrderDto' which only contains one address field.

Automapper complex objects

I've begun implementing this;
Automapper, mapping to a complex object but figured there must be a better way.
So I created this;
Mapper.CreateMap<StoreTransportWindow, CSVWindow>()
.ForMember(dest => dest.DC, opt => opt.ResolveUsing(fa => fa.DC.number))
.ForMember(dest => dest.Type, opt => opt.ResolveUsing(fa => fa.Type))
;
Mapper.CreateMap<Store, CSVStore>()
.ForMember(dest => dest.StoreName, opt => opt.ResolveUsing(fa => fa.name))
.ForMember(dest => dest.StoreNumber, opt => opt.ResolveUsing(fa => fa.number))
;
Now I'd like to use the above mappings in the primary map;
Mapper.CreateMap<Store, CSVLineObject>()
.ForMember( dest => dest.store, opt => opt.ResolveUsing(/* This is where I'd like to use the above Store to CSVStore mapping */))
;
Is this possible?
edit
public class CSVStore
{
public string StoreNumber { get; set; }
public string StoreName { get; set; }
}
public class CSVWindow
{
public string Type { get; set; }
public string DC { get; set; }
public string TPC { get; set; }
public class CSVLineObject
{
public CSVStore store { get; set; }
public List<CSVWindow> storeWindows { get; set; }
As mentioned in the comment, the initial mappings should probably be more like:
Mapper.CreateMap<StoreTransportWindow, CSVWindow>()
.ForMember(dest => dest.DC, opt => opt.MapFrom(src => src.DC.number));
// Mapping for property Type not required
Mapper.CreateMap<Store, CSVStore>()
.ForMember(dest => dest.StoreName, opt => opt.MapFrom(src => src.name))
.ForMember(dest => dest.StoreNumber, opt => opt.MapFrom(src => src.number));
Now say you have the following:
public class Source
{
public Store Store { get; set; }
}
public class Destination
{
public CSVStore Store { get; set; }
}
Then the following mapping will suffice (as you've already defined the nested mapping Store to CSVStore):
Mapper.CreateMap<Source, Destination>();
However if Destination was more like this:
public class Destination
{
public CSVStore CSVStore { get; set; }
}
Then you'll need to explicitly define the properties to be mapped:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.CSVStore, opt => opt.MapFrom(src => src.Store));
(Note that the mapping from Store to CVStore is applied automatically.)
If for some reason you do need to explicitly define a nested mapping, you can do something like this:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.destproperty,
opt => opt.MapFrom(
src => Mapper.Map<SrcType, DestType>(src.srcproperty));
I have needed to use that at times, but not very often as the default functionality takes care of it for you automatically.
I can provide more details if required if you can expand on your requirements.

Using Automapper to map a property of a collection to an array of primitives

Given the following set of classes:
class Parent
{
string Name { get; set; }
List<Child> children { get; set; }
}
class Child
{
short ChildId { get; set; }
string Name { get; set; }
}
class ParentViewModel
{
string Name { get; set; }
short[] ChildIds { get; set; }
}
When I call
Mapper.Map<Parent, ParentViewModel>(vm);
Is it possible to get AutoMapper to translate the list of Child.ChildId to ParentViewModel.ChildIds?
I've tried doing something like this:
Mapper.CreateMap<Child, short>()
.FromMember(dest => dest, opt => opt.MapFrom(src => src.ChildId));
Mapper.CreateMap<Parent, ParentViewModel>()
.FromMember(dest => dest.ChildIds, opt => opt.MapFrom(src => new[] {src.children}));
But I get an error saying it doesn't know how to convert a list of Child objects to an int16. Any suggestions?
Use a LINQ query to grab just the ChildIds:
.ForMember(d => d.ChildIds, o => o.MapFrom(s => s.Children.Select(c => c.ChildId).ToArray()));

Categories

Resources