Automapper - need to map a parent property when mapping children - automapper

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

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".

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?

How to map related data with AutoMapper

I'm trying to map three entity models into one viewmodel using AutoMapper. The final output should be a recursive category tree with products in the categories. The category tree is working, but the Products-property of the viewmodel is null. My query is returning categories and products, so I'm thinking that the mapping doesn't know how to map the products to the viewmodel.
My entity models:
public class ProductCategory
{
public int Id { get; set; }
public int SortOrder { get; set; }
public string Title { get; set; }
[ForeignKey(nameof(ParentCategory))]
public int? ParentId { get; set; }
// Nav.props:
public ProductCategory ParentCategory { get; set; }
public ICollection<ProductCategory> Children { get; set; }
public List<ProductInCategory> ProductInCategory { get; set; }
}
public class ProductInCategory
{
public int Id { get; set; }
public int ProductId { get; set; }
public int SortOrder { get; set; }
public int ProductCategoryId { get; set; }
public bool IsProductCategoryFrontPage { get; set; }
// Nav.props.
public Product Product { get; set; }
public ProductCategory ProductCategory { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public string Info { get; set; }
public decimal Price { get; set; }
// Nav.prop:
public List<ProductInCategory> InCategories { get; set; }
}
My viewmodel:
public class ViewModelProductCategory
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public string ProductCountInfo
{
get
{
return Products != null && Products.Any() ? Products.Count().ToString() : "0";
}
}
public ViewModelProductCategory ParentCategory { get; set; }
public IEnumerable<ViewModelProductCategory> Children { get; set; }
public IEnumerable<ViewModelProduct> Products { get; set; }
}
I have tried this mapping (maps categories, but no products):
CreateMap<ProductCategory, ViewModelProductCategory>();
CreateMap<ViewModelProductCategory, ProductCategory>();
I have tried this mapping (maps categories, but no products):
CreateMap<ProductCategory, ViewModelProductCategory>()
.ForMember(dto => dto.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dto => dto.ParentId, opt => opt.MapFrom(src => src.ParentId))
.ForMember(dto => dto.Title, opt => opt.MapFrom(src => src.Title))
.ForMember(dto => dto.SortOrder, opt => opt.MapFrom(src => src.SortOrder))
.ForMember(dto => dto.Children, opt => opt.MapFrom(src => src.Children))
.ForMember(dto => dto.Products, opt => opt.MapFrom(src => src.ProductInCategory));
Looks like you want to skip the junction (link) entity in the view model.
To do that, first create a mapping from Product to ViewModelProduct and then define a mapping from ProductCategory.ProductInCategory to ViewModelProductCategory.Products using projection converting List<ProductInCategory> to IEnumerable<Product>. AutoMapper will take care converting IEnumerable<Product> to List<ViewModelProduct> the same way as if you were querying Products:
CreateMap<Product, ViewModelProduct>();
CreateMap<ProductCategory, ViewModelProductCategory>()
.ForMember(dst => dst.Products, opt => opt.MapFrom(
src => src.ProductInCategory.Select(pc => pc.Product)));

Automapper How to devide properties from one object to properties of two objects same type

I have one object AllDriversDetails that contains data for two driver: First and Second.
public class AllDriversDetails
{
public string FirstDriverId { get; set; }
public string FirstDriverName { get; set; }
public string SecondDriverId { get; set; }
public string SecondDriverName { get; set; }
}
I have to convert AllDriversDetails to ComplexDriversDetails that has two properties of same type
public class ComplexDriversDetails
{
public DriverDetails FirstDriver { get; set; }
public DriverDetails SecondDriver { get; set; }
}
public class DriverDetails
{
public string Id { get; set; }
public string Name { get; set; }
}
Is it possible to do with automapper?
I should be no problem. Try something like this
Mapper.CreateMap<AllDriversDetails, ComplexDriversDetails>()
.ForMember(m => m.FirstDriver, opt => opt.MapFrom(src => new DriverDetails {Id = src.FirstDriverId, Name = FirstDriverName }))
.ForMember(m => m.SecondDriver , opt => opt.MapFrom(src => new DriverDetails {Id = src.SecondDriverId, Name = SecondDriverName }))
You can find more information here: http://cpratt.co/using-automapper-creating-mappings/

Resources