AutoMapper - How map complex objects to simpler model - c#

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/

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 before/after map callbacks during mapping

I'm using ASP.NET Core and Automapper 6.1.0 ,
I have two types that look like this
public class ExampleDTO
{
public Guid Id { get; set; }
public ProviderDTO Provider { get; set; }
}
public class Example
{
public Guid Id { get; set; }
public Guid Provider { get; set; }
}
ProviderDTO class (which is irelevant in this case)
public class ProviderDTO
{
public Guid Id { get; set; }
public string Name { get; set; }
}
AutoMapper configuration looks like this:
CreateMap<Example, ExampleDTO>().ForMember(x => x.Provider, opt => opt.Ignore());
CreateMap<ExampleDTO, Example>().ForMember(dest => dest.Provider,
opt => opt.MapFrom(src => src.Provider.Id));
When I map from Example to ExampleDTO, I want to pass the value for ProviderDTO type.
I tried something like this.
_mapper.Map<ExampleDTO>(example, opt => opt.AfterMap((src, dest) => dest.Provider = myProvider));
I get this
'object' does not contain a defenition for 'Provider' and no extension method
Is this achievable? If yes, what am I doing wrong?
With the AutoMapper, you may need to provide both the source and destination type, such as:
_mapper.Map<Example, ExampleDTO>(example, opt => {
opt.AfterMap((src, dest) => dest.Provider = myProvider))
});

C# - Automapper - Map element with condition

I have two classes:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Quantity { get; set; }
};
public class Customer_
{
public string FirstNam { get; set; }
public string LastNam { get; set; }
public int Quantity { get; set; }
}
And a mapping between the two with Automapper library :
Customer[] data = new Customer[3];
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, Customer_>()
.ForMember(dest => dest.FirstNam, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.LastNam, opt => opt.MapFrom(src => src.LastName));
});
IMapper mapper = config.CreateMapper();
Customer_[] desti = mapper.Map<Customer[], Customer_[]>(data);
It works, but how to map only the elements of my array data that meet a condition ? For example : map only the element who have a Quantity > x
Thank's
Typically I'd do this before you map into AutoMapper:
Customer_[] desti = mapper.Map<Customer[], Customer_[]>(
data.Where(c => c.Quantity > 10).ToArray());
Don't try to put this sort of intelligence into AutoMapper, it's confusing and will lead to bugs.

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.

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