Handling multiple date formats in Automapper - automapper

For example, suppose we have a source type of:
public class Source
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public string Value3 { get; set; }
}
And we would like to map it to:
public class Destination
{
public DateTime Value1 { get; set; }
public DateTime Value2 { get; set; }
public DateTime Value3 { get; set; }
}
Each Value is converted using a different date format.
CreateMap<string, DateTime>().ConvertUsing(new DateTimeConverter1());
CreateMap<string, DateTime>().ConvertUsing(new DateTimeConverter2());
CreateMap<string, DateTime>().ConvertUsing(new DateTimeConverter3());
CreateMap<DateTime, string>().ConvertUsing(dt => dt.ToString(DateStringAttribute.DateFormatString1));
CreateMap<DateTime, string>().ConvertUsing(dt => dt.ToString(DateStringAttribute.DateFormatString2));
CreateMap<DateTime, string>().ConvertUsing(dt => dt.ToString(DateStringAttribute.DateFormatString3));
How can I get the correct format used (going both ways) when I can only specify string and DateTime?
Can I somehow access the property name so I can select the appropriate conversion format?

I don't know if this is the best way to do it but this seemed to work.
CreateMap<Destination, Source>()
.ForMember(dest => dest.Value1, opt => opt.MapFrom(src =>src.Value1.HasValue?src.Value1.Value.ToString(DateStringAttribute.DateFormat1):""))
.ForMember(dest => dest.Value2, opt => opt.MapFrom(src =>src.Value2.HasValue?src.Value2.Value.ToString(DateStringAttribute.DateFormat2):""))
.ForMember(dest => dest.Value3, opt => opt.MapFrom(src =>src.Value3.HasValue?src.Value3.Value.ToString(DateStringAttribute.DateFormat3):""))
.ReverseMap();

Related

AutoMapper different level

Trying to map from Customer to CustomerDto but having issues with that extra layer in the source (I have no control over the source so I cannot align the two unfortunately).
public class Customer
{
public string Name { get; set; }
public AddressSet AddressSet { get; set; }
}
public class AddressSet
{
public AddressSetResults[] AddressSetResults { get; set; }
}
public class AddressSetResults
{
public string Street { get; set; }
public string HouseNumber { get; set; }
}
public class CustomerDto
{
public string Name { get; set; }
public AddressDto AddressDto { get; set; }
}
public class AddressDto
{
public string Street { get; set; }
public string HouseNumber { get; set; }
}
The following does not work for the AddressDto, any idea what I'm missing?
CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.AddressDto , opt => opt.MapFrom(src => src.AddressSet.AddressSetResults))
Two things:
1) Missing map from AddressSetResults to AddressDto
In order to map inner address you need to create map for these inner types as well:
CreateMap<AddressSetResults, AddressDto>();
2) Map from an element of AddressSetResults array, not from the array itself
This method:
.ForMember(
dest => dest.AddressDto,
opt => opt.MapFrom(src => src.AddressSet.AddressSetResults))
tells AutoMapper to map to AddressDto from AddressSetResults which is an array of AddressSetResults. This is incorrect, as AutoMapper will not know how to map from array of elements to just one element. Unless you create a map for that too, which would not be a good solution.
Assuming that AddressSetResults will contain up to one address you can fix that adding just one more call FirstOrDefault() to the end of mapping expression:
.ForMember(
dest => dest.AddressDto,
opt => opt.MapFrom(src => src.AddressSet.AddressSetResults.FirstOrDefault()))
FirstOrDefault() needs System.Linq namespace.
Why not just First()? If source AddressSetResults array would contain no elements, then mapping would fail resulting in exception as no elements would be found to satisfy the First() method call. Making it resistant to no elements scenario with FirstOrDefault() is more secure solution.
Final, working configuration:
CreateMap<Customer, CustomerDto>()
.ForMember(
dest => dest.AddressDto,
opt => opt.MapFrom(src => src.AddressSet.AddressSetResults.FirstOrDefault()));
CreateMap<AddressSetResults, AddressDto>();

Automapper 8 mapping not working properly

I have two model classes, when I try to map different properties of different name by using Automapper ForMember method. It throws an automapper configuration validation exception on the mapping of different property.
I have tried a lot but It does not help.I do not know why It is throwing an exception when I try to map Quantity property with Quntity property. but when I put same name of the property in both the model classes then it works
Below is located all the model classes, exception and configurations regarding automapper.
Could you please help me, that how to solve problem?
public class ProductModel
{
public ProductModel()
{
Id = GuidContext.Current.NewGuid();
ProductHistory = new HashSet<ProductHistoryModel>();
}
public Guid Id { get; set; }
public string ProductCode { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public decimal? Price { get; set; }
public int? Quntity { get; set; }
public Guid ProductCategoryId { get; set; }
public Guid? BrandId { get; set; }
public Guid ProductAttributeId { get; set; }
public virtual BrandModel Brand { get; set; }
public virtual ProductCategoryModel ProductCategory { get; set; }
public virtual ProductAttributeModel ProductAttribute { get; set; }
public virtual ICollection<ProductHistoryModel> ProductHistory { get; set; }
}
The another class is
public class ProductModel
{
public string Name { set; get; }
//public List<string> Attributes { set; get; }
//public string Brand { get; set; }
public decimal? Price
{
get; set;
}
public int? Quantity { get; set; }
}
}
and the mapping configuration is
public class ProductModelMapConfigurator : Profile, IMapConfigurator
{
public void Configure()
{
Mapper.Initialize(cfg =>
{
CreateMap<StandardizeInventory.Models.Product.ProductModel, Models.ProductModel>()
//.ForMember(dest => dest.Brand, opt => opt.MapFrom(src => src.Brand.Name))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.Price, opt => opt.MapFrom(src => src.Price))
.ForMember(dest => dest.Quantity, opt => opt.MapFrom(src => src.Quntity));
//.AfterMap((src, dest) => {
// dest.Attributes = src.ProductAttribute.ProductAttributeValue.Select(x => x.Value).ToList();
//});
CreateMap<Models.ProductModel, StandardizeInventory.Models.Product.ProductModel>();
});
}
}
Below is the Exception Details
AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
==========================================================================================
AutoMapper created this type map for you, but your types cannot be mapped using the current configuration.
ProductModel -> ProductModel (Destination member list)
StandardizeInventory.Models.Product.ProductModel -> InventoryStoreApi.Models.ProductModel (Destination member list)
Unmapped properties:
Quantity
at AutoMapper.ConfigurationValidator.AssertConfigurationIsValid(IEnumerable`1 typeMaps) in
Any help would be appreciated. Thanks
You're using a Profile wrong, see the documentation on Profiles
Your profile should look like:
public class ProductModelMapConfigurator : Profile, IMapConfigurator
{
public ProductModelMapConfigurator()
{
CreateMap<StandardizeInventory.Models.Product.ProductModel, Models.ProductModel>()
//.ForMember(dest => dest.Brand, opt => opt.MapFrom(src => src.Brand.Name))
.ForMember(dest => dest.Quantity, opt => opt.MapFrom(src => src.Quntity));
//.AfterMap((src, dest) => {
// dest.Attributes = src.ProductAttribute.ProductAttributeValue.Select(x => x.Value).ToList();
//});
CreateMap<Models.ProductModel, StandardizeInventory.Models.Product.ProductModel>();
}
}
Get rid of that Mapper.Initialize call from inside your Profile, and change the profile to use a constructor, not whatever that Configure method is. You also don't need MapFrom when the names match, that's the "Auto" of "AutoMapper".

Automapper - need to map a parent property when mapping children

The basic problem is that I have properties on my source parent entities that I need to map to my destination child DTOs when the child entities are mapped to the destination child DTOs. The child entities do not have navigation properties to their parent. As you can see below, the parent has a field of the child type. You can see in the code below that ParentSource has a property called TitleImage of type ImageSource. So linkage is one directional. In my code, I use the ImageSource everywhere. There are at least 15 different entities with properties of type ImageSource. I normalized my database. The ImageSource entity does not have X and Y coordinates because only a few, maybe 5 out of 30, places actually need those extra values. In those few places, I added the X and Y on the parent as you see on the ParentSource class with the TitleImageX and TitleImageY.
I could easily do what I want with a loop, and that is what I am doing now, but I would love to use Automapper for this if I could. It would be way less code.
Here are my classes:
public class ParentSource
{
public string Id { get; set; }
public string Name { get; set; }
public ImageSource HomeImage { get; set; }
public ImageSource TitleImage { get; set; }
//These should be copied to the child object
public int TitleImageX { get; set; }
public int TitleImageY { get; set; }
}
public class ParentDest
{
public string ParentId { get; set; }
public string DisplayName { get; set; }
public ImageDest HomeImage { get; set; }
public ImageDest TitleImage { get; set; }
}
public class ImageSource
{
public string Id { get; set; }
public string Url { get; set; }
public decimal Height { get; set; }
public decimal Width { get; set; }
}
public class ImageDest
{
public string ImageId { get; set; }
public string ImageUrl { get; set; }
public decimal Height { get; set; }
public decimal Width { get; set; }
//Not all images have Coordinate
public decimal XCoordinate { get; set; }
public decimal YCoordinate { get; set; }
}
Here are what I have so far for doing the mappings. I don't know how to copy the X & Y coordinates from the parent to the child.
CreateMap<ParentSource, ParentDest>()
.ForMember(
dest => dest.ParentId,
opt => opt.MapFrom(src => src.Id))
.ForMember(
dest => dest.DisplayName,
opt => opt.MapFrom(src => src.Name));
CreateMap<ImageSource, ImageDest>()
.ForMember(
dest => dest.ImageId,
opt => opt.MapFrom(src => src.Id))
.ForMember(
dest => dest.ImageUrl,
opt => opt.MapFrom(src => src.Url));
Thank you for your time and help.
Or you can do it in config (not tried or tested) which you may prefer, but there is no check for null on TitleImage
CreateMap<ParentSource, ParentDest>()
.ForMember(
dest => dest.ParentId,
opt => opt.MapFrom(src => src.Id))
.ForMember(
dest => dest.DisplayName,
opt => opt.MapFrom(src => src.Name))
.ForMember(dest=>dest.TitleImage,
opt=>opt.MapFrom(src=>new ImageDest
{
Width = src.TitleImage.Width,
Height = src.TitleImage.Height,
ImageId = src.TitleImage.Id,
ImageUrl = src.TitleImage.Url,
XCoordinate = src.TitleImageX,
YCoordinate = src.TitleImageY
}
));
From what I gather you want the X&Y from the parent into the child. You can use a converter class for this.
Converter:
public class ParentConverter : ITypeConverter<ParentSource, ParentDest>
{
public ParentDest Convert(ParentSource source, ParentDest destination, ResolutionContext context)
{
if (destination == null)
destination = new ParentDest();
destination.DisplayName = source.Name;
destination.ParentId = source.Id;
if (source.TitleImage != null)
{
destination.TitleImage = context.Mapper.Map<ImageDest>(source.TitleImage);
destination.TitleImage.XCoordinate = source.TitleImageX;
destination.TitleImage.YCoordinate = source.TitleImageY;
}
return destination;
}
}
Mapping:
CreateMap<ParentSource, ParentDest>()
.ConvertUsing<ParentConverter>();
CreateMap<ImageSource, ImageDest>()
.ForMember(
dest => dest.ImageId,
opt => opt.MapFrom(src => src.Id))
.ForMember(
dest => dest.ImageUrl,
opt => opt.MapFrom(src => src.Url);
});

How to use same complex type as a property of several output types but different source types

I have a class for CountryAndCity:
public class CountryAndCity
{
public Country Country { get; set; }
public City City { get; set; }
public ZipCode ZipCode { get; set; }
}
I am using this class in several output classes, for example:
public class OutputClassA
{
public CountryAndCity CountryAndCity { get; set; }
}
public class OutputClassB
{
public CountryAndCity CountryAndCity { get; set; }
}
In addition, i have some "Input Classes" which includes the same relevant fields for mapping with additional other fields:
public class InputClassA : ICountryAndCity
{
...Some other properties.....
public int? CountryId { get; set; }
public string CountryDesc { get; set; }
public int? CityId { get; set; }
public string CityDesc { get; set; }
...Some other properties.....
}
public class InputClassB : ICountryAndCity
{
...Some other properties.....
public int? CountryId { get; set; }
public string CountryDesc { get; set; }
public int? CityId { get; set; }
public string CityDesc { get; set; }
...Some other properties.....
}
i didn't want to duplicate the code for every CreateMap statement for CountryAndCity property mapping for every Input and output combination types so i decided to use an interface that all the "input types" implements.
i created this configuration and its working great if i implement ICountryAndCity on all the relevant "input classes" and using "Mapper.Map" for the interface inside the CreateMap function.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<InputClassA, OutputClassA>()
.ForMember(dest => dest.CountryAndCity,
opts => opts.MapFrom(src => Mapper.Map<ICountryAndCity, CountryAndCity>((ICountryAndCity)src)));
cfg.CreateMap<InputClassB, OutputClassB>()
.ForMember(dest => dest.CountryAndCity,
opts => opts.MapFrom(src => Mapper.Map<ICountryAndCity, CountryAndCity>((ICountryAndCity)src)));
cfg.CreateMap<ICountryAndCity, CountryAndCity>()
.ForMember(dest => dest.Country,
opts => opts.MapFrom(
src => new Country
{
CountryId = src.CountryId,
CountryDesc = src.CountryDesc
}))
.ForMember(dest => dest.City,
opts => opts.MapFrom(
src => new City
{
CityId = src.CityId,
CityDesc = src.CityDesc
}))
.ForMember(dest => dest.ZipCode,
opts => opts.MapFrom(
src => new ZipCode
{
ZipCodeId = src.ZipCodeId,
ZipCodeDesc = src.ZipCodeDesc
}));
}
I am sure that there is other better way to do it using AutoMapper without using interface. Can someone help with that?

Using AutoMapper to map multiple collection properties of the same type

I need to map to properties that are both collections of objects of the same type.
For example:
Destination:
public class MyNewObject
{
Collection TypeCollection1 { get; set; }
Collection TypeCollection2 { get; set; }
}
public class MyType
{
string Field1 { get; set; }
string Field2 { get; set; }
}
Source:
public class MyLegacyObject
{
Collection LegacyCollection1 { get; set; }
Collection LegacyCollection2 { get; set; }
}
public class MyLegacyType
{
string OldField1 { get; set; }
string OldField2 { get; set; }
}
For non-collections, I'm used to doing something like this:
Mapper.CreateMap()
.ForMember(dest => dest.TypeCollection1, opt => opt.MapFrom(
src => new Collection
{
// is there some kind of ForEach thing I can do???
Field1 = src.OldField1,
Field2 = src.OldField2
??? // this obviously doesn't work because these are the properties on MyType, not the collection
}));
Mapper.CreateMap<MyLegacyType, MyType>()
.ForMember(dest => dest.Field1, opt => opt.MapFrom(src => src.OldField1))
.ForMember(dest => dest.Field2, opt => opt.MapFrom(src => src.OldField2));
Mapper.CreateMap<MyLegacyObject, MyNewObject>()
.ForMember(dest => dest.TypeCollection1, opt => opt.MapFrom(src => src.LegacyCollection1))
.ForMember(dest => dest.TypeCollection2, opt => opt.MapFrom(src => src.LegacyCollection2))
I think I solved my own issue. This should do it.

Resources