How to map related data with AutoMapper - 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)));

Related

Automapper flatten from three levels to two levels

I use automapper v. 9 and I need to transform a 3 levels structure to a 2 levels structure.
I already tried with the IncludeMembers option, but I can't achieve the result I want.
Here is my model classes (simplified)
public partial class Mail
{
public Mail()
{
Mail_Files = new HashSet<Mail_Files>();
}
[Key]
public int Id { get; set; }
public string Oggetto { get; set; }
public string Testo { get; set; }
[InverseProperty("IdMailNavigation")]
public virtual ICollection<Mail_Files> Mail_Files { get; set; }
}
public partial class Mail_Files
{
[Key]
public int Id { get; set; }
public int IdMail { get; set; }
public int IdFile { get; set; }
[ForeignKey(nameof(IdFile))]
[InverseProperty(nameof(Files.Mail_Files))]
public virtual Files IdFileNavigation { get; set; }
[ForeignKey(nameof(IdMail))]
[InverseProperty(nameof(Mail.Mail_Files))]
public virtual Mail IdMailNavigation { get; set; }
}
public partial class Files
{
public Files()
{
Mail_Files = new HashSet<Mail_Files>();
}
[Key]
public int Id { get; set; }
public int IdTipoFile { get; set; }
public string FileName { get; set; }
public int? FileSize { get; set; }
[InverseProperty("IdFileNavigation")]
public virtual ICollection<Mail_Files> Mail_Files { get; set; }
}
I have to flatten this object to this:
public class MailFileResource
{
public int Id { get; set; }
public string FileName { get; set; }
}
public class MailResource
{
public int Id { get; set; }
public string Testo { get; set; }
public IEnumerable<MailFileResource> Files { get; set; }
}
I do it this way:
CreateMap<Files, MailFileResource>()
.ForMember(d => d.FileName, opt => opt.MapFrom(s => s.FileName))
.ForMember(d => d.Id, opt => opt.MapFrom(s => s.Id));
CreateMap<Mail_Files, MailFileResource>().IncludeMembers(s => s.IdFileNavigation);
CreateMap<Mail, MailResource>()
.ForMember(d => d.Files , opt => opt.MapFrom(s => s.Mail_Files))
.ForMember(d => d.Protezione, opt => opt.MapFrom(s => s.Protezione));
My mistake was to use IncludeMembers in Mail -> MailResource mapping too

How can I Automap a missmatch between two models with a different number of fields?

I use Visual Studio 2019 and .net core 3.1. I have a couple of related models, CustomerCountries and CustomerRegions in a one-to-many relationship, i.e. 1 CustomerCountries has many CustomersRegions.
In the views of the regions I want to show the name of the countries, not the id, that's why I created a ViewModel:
public class CustomerCountryRegionVM
{
public int IdCustomerRegion { get; set; }
public string CustomerRegion { get; set; }
public int IdCustomerCountry { get; set; }
public string CustomerCountry { get; set; }
}
The issue is that the regions model does not have the CustomerCountry field, it has the foreign primary key IdCustomerCountry. So I don't know how to do the mapping, I've tried it but I get a missmatch. One has 4 fields and the other 3. Do I have to make a discount for the viewmodel? or is there another option?
public class CustomerRegionsDto
{
public int IdCustomerRegion { get; set; }
public string CustomerRegion { get; set; }
[ForeignKey("CustomerCountryId")]
public int IdCustomerCountry { get; set; }
public CustomerCountriesDto CustomerCountryDto { get; set; }
}
public class CustomerRegions
{
[Key]
public int IdCustomerRegion { get; set; }
[StringLength(50, ErrorMessage = "Longitud máxima para la región: 50")]
public string CustomerRegion { get; set; }
[ForeignKey("IdCustomerCountry")]
public int IdCustomerCountry { get; set; }
public CustomerCountries CustomerCountry { get; set; }
}
Automapping class
CreateMap<CustomerRegionsDto, CustomerRegions>();
CreateMap<CustomerRegions, CustomerRegionsDto>();
CreateMap<CustomerCountryRegionVM, CustomerRegions>();
CreateMap<CustomerRegions, CustomerCountryRegionVM>();
CreateMap<CustomerCountryRegionVM, CustomerCountryRegionDto>();
CreateMap<CustomerCountryRegionDto, CustomerCountryRegionVM>();
In the controller:
var model = _mapper.Map<CustomerCountryRegionVM, CustomerRegionsDto>(ccr);
The other model:
public class CustomerCountries
{
public int IdCustomerCountry { get; set; }
[StringLength(50, ErrorMessage = "Longitud máxima para el país: 50")]
public string CustomerCountry { get; set; }
public ICollection<CustomerRegions> CustomerRegions { get; set; }
}
You can map particular property
CreateMap<BusinessService.Model.PermitDashboard, BusinessService.Model.Permit>()
.ForMember(x => x.PermitId, opt => opt.MapFrom(z => z.PermitId))
.ForMember(x => x.PermitTitle, opt => opt.MapFrom(z => z.PermitTitle))
.ForPath(x => x.PermitType.PermitTypeName, opt => opt.MapFrom(z => z.PermitType))
.ForMember(x => x.EffectiveDt, opt => opt.MapFrom(z => z.EffectiveDt))
.ForMember(x => x.ExpirationDt, opt => opt.MapFrom(z => z.ExpirationDt))
.ReverseMap();

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?

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/

map collection of model to viewmodel with automapper

iwant to map collection of model to viewmodel with automapper
this is my code
public class LanguageViewModel
{
public string Name { get; set; }
public IList<Word> Categories { set; get; }
public IList<string> PictureURL { set; get; }
}
and this my model
public class Word : BaseFieldsTables
{
public string Text { get; set; }
public virtual Category Category { get; set; }
public int CategoryID { get; set; }
public virtual Language Language { get; set; }
public int LanguageID { get; set; }
public virtual ICollection<Picture> Pictures { get; set; }
[InverseProperty("MainWord")]
public virtual ICollection<RelationshipBetweenWords> MainWords { get; set; }
[InverseProperty("RelatedWord")]
public virtual ICollection<RelationshipBetweenWords> RelatedWords { get; set; }
}
Fot his example you start with top domain model and map it with inner collections called Nested mappings.
mapper.CreateMap<Word , LanguageViewModel>()
.ForMember(dest => dest.Name, src => src.MapFrom(s => s.Text));
For inner collections within Word domain model, you need to define separate mappings for Categories and Pictures.
mapper.CreateMap<Picture, PictureModel>()
.ForMember(dest => dest.PictureURL , src => src.MapFrom(x => x.Picture.Urltext));
mapper.CreateMap<Category, CategoryModel>()
.ForMember(dest => dest.CategoryId, src => src.MapFrom(x => x.Category.CategoryInt));
and for all remaining inner collections you need to define mapping to make it work

Resources