mapping sub field issue - c#

I have problem when mapping my entities to my models.
entity class 1
enter public partial class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public int? CityId { get; set; }
public virtual City City { get; set; }
}
class 2
public partial class City
{
public City()
{
Contact = new HashSet<Contact>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Contact> Contact { get; set; }
}
The model for my contact is
public class ContactViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public KeyValueGeneric<int?, string> City = new KeyValueGeneric<int?, string>();
}
I'm trying to map it like this:
CreateMap<ContactViewModel, Contact>()
.ForMember(dest => dest.CityId, opts => opts.MapFrom(src => src.City.Id));
CreateMap<Contact, ContactViewModel>()
.ForMember(dest => dest.City.Id, input => input.MapFrom(src => src.CityId))
.ForMember(dest => dest.City.Value, input => input.MapFrom(src => src.City.Name));
but I'm getting:
must resolve to top-level member and not any child object message
I'm gonna appreciate any help

Assume it is because you are mapping to child properties:
dest.City.Id
Why not create a corresponding model for City (e.g. CityViewModel) and map to it directly?
CreateMap<City, CityViewModel>();
CreateMap<Contact, ContactViewModel>()
.ForMember(dest => dest.City, input => input.MapFrom(src => src.City));
If you do want to map to child properties, you may need to use ForPath() instead of ForMember(). E.g:
.ForPath(dest => dest.City.Id, input => input.MapFrom(src => src.CityId))

Related

Nested collection mapping fails with automapper

Somehow the mapping fails with the error message:
One or more errors occurred. (The following member on Data.Shop cannot be mapped:
Images
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type Data.Shop.
After having spent some time tinkering with it and searching for a solution on the official documentation + SO, I feel like I either messed up / forgot with a particular call in the profile, or need to resort to creating a custom map.. What do you guys think?
Configuration code that I used:
Entities
public class Shop
{
public Guid Id { get; set; }
public string Name { get; set; }
public Specialty Specialty { get; set; } = new Specialty();
public Address Address { get; set; } = new Address();
public string Owner { get; set; }
public List<Image> Images { get; set; } = new List<Image>();
public List<Tag> Tags { get; set; } = new List<Tag>();
public List<Promotion> Promotions { get; set; } = new List<Promotion>();
}
public class Image
{
public Guid Id { get; set; }
public string Title { get; set; }
public byte[] ImageData { get; set; }
}
DTOs
public class ShopDto
{
[Required]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Specialty { get; set; }
[Required]
public int HouseNumber { get; set; }
[Required]
public string Street { get; set; }
[Required]
public string City { get; set; }
[Required]
public short Plz { get; set; }
[Required]
public string Owner { get; set; }
public List<ImageDto> Images { get; set; } = new List<ImageDto>();
public List<TagDto> Tags { get; set; } = new List<TagDto>();
public List<PromotionDto> Promotions { get; set; } = new List<PromotionDto>();
}
public class ImageDto
{
[Required]
public Guid Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public byte[] ImageData { get; set; }
}
Profile code
CreateMap<Image, ImageDto>();
CreateMap<Shop, ShopDto>()
.ForMember(dest => dest.City, member => member.MapFrom(source => source.Address.City))
.ForMember(dest => dest.Plz, member => member.MapFrom(source => source.Address.Plz))
.ForMember(dest => dest.Street, member => member.MapFrom(source => source.Address.Street))
.ForMember(dest => dest.HouseNumber, member => member.MapFrom(source => source.Address.HouseNumber))
.ForMember(dest => dest.Images, opt => opt.MapFrom(source => source.Images))
.ReverseMap()
.ForMember(src => src.Id, dest => dest.Ignore())
.ForMember(src => src.Promotions, dest => dest.MapFrom(src => src.Promotions))
.ForMember(src => src.Tags, dest => dest.MapFrom(src => src.Tags))
.ForMember(src => src.Images, dest => dest.MapFrom(src => src.Images))
.ForPath(src => src.Specialty.Name, dest => dest.MapFrom(src => src.Specialty))
.ForPath(src => src.Address.City, dest => dest.MapFrom(src => src.City))
.ForPath(src => src.Address.Plz, dest => dest.MapFrom(src => src.Plz))
.ForPath(src => src.Address.Street, dest => dest.MapFrom(src => src.Street))
.ForPath(src => src.Address.HouseNumber, dest => dest.MapFrom(src => src.HouseNumber));
You have defined a mapping from Shop to ShopDto but appear to be attempting to map ShopDto to Shop. You need define mappings in both directions or swap the template arguments. Of course you also need a mapping from ImageDto to Image.
Additionally you can remove this part:
.ForMember(src => src.Images, dest => dest.MapFrom(src => src.Images))
AutoMapper is smart enough to map collections if it has a mapping for the collection element type.

ASP .Net Core: Many-to-Many relationship and AutoMapper

I have 2 entities connected by a many to many relationship.
First class:
public class ArticleCategory
{
public int Id {get; set; }
public string MainCategoryName { get; set; }
public List<ArticleCategorySubcategory> ArticleCategorySubcategories { get; set; } = new List<ArticleCategorySubcategory>();
public bool IsActive { get; set; }
}
Second class:
public class ArticleSubcategory
{
public int Id { get; set; }
public string SubcategoryName { get; set; }
public List<ArticleCategorySubcategory> ArticleCategorySubcategories { get; set; } = new List<ArticleCategorySubcategory>();
}
And relationship (many to many):
public class ArticleCategorySubcategory : BaseHistoryEntity
{
public int Id { get; set; }
public int ArticleCategoryId { get; set; }
public ArticleCategory ArticleCategory { get; set; }
public int ArticleSubcategoryId { get; set; }
public ArticleSubcategory ArticleSubcategory {get; set;}
}
And I have also 1 DTO:
public class ArticleCategoryResult
{
public string CategoryName { get; set; }
public List<string> Subcategories { get; set; }
public bool IsActive { get; set; }
}
I want to use AutoMapper to list the names of subcategories. I tried something like this but I get an empty list.
My Automapper code:
CreateMap<ArticleCategory, ArticleCategoryResult>()
.ForMember(dst => dst.CategoryName, opt => opt.MapFrom(src => src.MainCategoryName))
.ForMember(dst => dst.IsActive, opt => opt.MapFrom(src => src.IsActive))
.ForMember(dst => dst.Subcategories, src => src.MapFrom(mbr => mbr.ArticleCategorySubcategories.Select(x => x.ArticleSubcategory.SubcategoryName)));
Result on view as json:
{
"categoryName": "Example category 6",
"subcategories": [],
"isActive": true
}
This is what my configuration looks like for these tables:
public void Configure(EntityTypeBuilder<ArticleCategorySubcategory> builder)
{
builder
.HasKey(x => x.Id);
builder
.HasOne(x => x.ArticleCategory)
.WithMany(x => x.ArticleCategorySubcategories)
.HasForeignKey(x => x.ArticleCategoryId);
builder
.HasOne(x => x.ArticleSubcategory)
.WithMany(x => x.ArticleCategorySubcategories)
.HasForeignKey(x => x.ArticleSubcategoryId);
}
How can I list the names of subcategories using AutoMapper?
You need to add .Include(x=>x.ArticleSubcategory) before your .Select(.. as the related objects are not tracked and are treated as undefined.

Automapper map nested different object properties

I have the following Company and its nested object CompanyEmployee:
public class Company
{
public string Id { get; set; }
public string LogoPath { get; set; }
public string RammeId { get; set; }
public List<CompanyEmployee> Employees { get; set; }
}
public class CompanyEmployee
{
public string Id { get; set; }
[ForeignKey("Company")]
public string CompanyId { get; set; }
public Company Company { get; set; }
public string EmployeeId { get; set; }
}
Now I want to map the Entities to Dtos defined as the following objects CompanyDto and its nested object EmployeeDto:
public class CompanyDto
{
[Required]
public string Id { get; set; }
[Required]
public string Name { get; set; }
public string LogoPath { get; set; }
public string RammeId { get; set; }
public IFormFile FormFile { get; set; }
public List<EmployeeDto> Employees { get; set; }
}
public class EmployeeDto
{
public string Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public List<RoleDto> Roles { get; set; }
}
My problem is the CompanyEmployee to EmployeeDto mapping.
How can I create a map that can take the property EmployeeId and map it to the Id property of EmployeeDto?
Currently, I have the following maps:
CreateMap<EmployeeDto, CompanyEmployee>(MemberList.Destination)
.ForMember(emp => emp.EmployeeId, opt => opt.MapFrom(ce => ce.Id));
CreateMap<CompanyDto, Company>(MemberList.Destination)
.ForMember(c => c.Employees.Select(e => e.CompanyId), opt => opt.MapFrom(cd => cd.Id));
CreateMap<Company, CompanyDto>(MemberList.Destination)
.ForMember(c => c.Id, opt => opt.MapFrom(cd => cd.Employees.First().CompanyId));
You want to create an AutoMapper Profile to configure each property mapping.
Create classes that inherit from Profile and put the configuration in the constructor.
For example:
public class EmployeeProfile : Profile
{
//Constructor
public EmployeeProfile()
{
//Mapping properties from CompanyEmployee to EmployeeDto
CreateMap<CompanyEmployee, EmployeeDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.EmployeeId));
//Mapping properties from EmployeeDto to CompanyEmployee
CreateMap<EmployeeDto, CompanyEmployee>()
.ForMember(dest => dest.EmployeeId, opt => opt.MapFrom(src => src.Id));
}
}
public class CompanyProfile : Profile
{
//Constructor
public CompanyProfile()
{
//Mapping properties from Company to CompanyDto
CreateMap<Company, CompanyDto>()
.ForMember(dest => dest.Employees, opt => opt.MapFrom(src => src.Employees));
//Mapping properties from CompanyDto to Company
CreateMap<CompanyDto, Company>()
.ForMember(dest => dest.Employees, opt => opt.MapFrom(src => src.Employees))
//Setting CompanyId
.AfterMap((src, dest) => {
foreach (var employee in dest.Employees)
{
employee.CompanyId = dest.Id;
}
});
}
}
AutoMapper Profile Configuration Documentation
Just create Profile and all properties which have the same name will mapped automatically. However, properties which do not have the same names, they should have custom mapping:
public class FromModelToDto : Profile
{
public FromModelToDto ()
{
CreateMap<CompanyEmployee, EmployeeDto>()
.ForMember(dest.Id, opts => opts.MapFrom(model => model.EmployeeId))
}
}
UPDATE:
If you want to map from Dto to Model, then you should create another mapping class:
public class FromDtoToModel : Profile
{
public FromDtoToModel ()
{
CreateMap<EmployeeDto, CompanyEmployee>()
.ForMember(dest.EmployeeId, opts => opts.MapFrom(model => model.Id))
}
}
You can read more about Automapper here.

Map variable from DTO class using LINQ

I have two classes called Participant and Screen.
public class Participant
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Screen> Screens { get; set; }
}
public class Screen
{
public int Id { get; set; }
public DateTime? SignedDateTime { get; set; }
}
And I use a DTO as below:
public class ParticipantForDashboardDto
{
public int Id { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime? ScreenDateTime { get; set; }
public ICollection<Screen> Screens { get; set; }
}
In here, since I don't have ScreenDateTime field in my Participant class, I decided to get this field from Screen class using AutoMapper. Here is my MappingProfile:
CreateMap<Participant, ParticipantForDashboardDto>()
.ForMember(dest => dest.ScreenDateTime, opt => {
opt.MapFrom(src => src.Screens.Select(x => x.SignedDateTime));
});
This returns 0001-01-01T00:00:00. I suspect that the SELECT query is not the one I should use. How can I map SignedDateTime from Screen class to ScreenDate in the dto?
Your currently using a queryable, which will return multiple result, you probably on need one, so you need to create a way to resolve the proper one, e.g sort and first or default:
CreateMap<Participant, ParticipantForDashboardDto>()
.ForMember(dest => dest.ScreenDateTime, opt => {
opt.MapFrom(src => src.Screens.Select(x => x.SignedDateTime)
.OrderByDescending(x => x)
.FirstOrDefault());
});

Expression must resolve to top-level member and not any child object's properties

I am trying to map an object with difficult structure to a simple object:
public class Contact
{
public string ContactName { get; set; }
public IEnumerable<Phone> Phones { get; set; }
}
public class Phone
{
public string Number { get; set; }
public PhoneType Type { get; set; }
}
public enum PhoneType
{
Phone,
Mobile
}
public class ContactDto
{
public string ContactName { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
}
Mapper.CreateMap<ContactDto, Contact>()
.ForMember(p => p.Phones.FirstOrDefault(k => k.Type.Equals(PhoneType.Mobile)).Number, r => r.MapFrom(s => s.MobileNumber))
.ForMember(p => p.Phones.FirstOrDefault(k => k.Type.Equals(PhoneType.Phone)).Number, r => r.MapFrom(s => s.PhoneNumber));
But I have an error in the title. Whats wrong?
The order of your generic parameters is wrong. The source should be the first and the destination is the latter.
Try this instead:
Mapper.CreateMap<Contact, ContactDto>()
.ForMember(dto => dto.PhoneNumber, dto => dto.MapFrom(src => src.Phones.First(p => p.Type == PhoneType.Phone).Number))
.ForMember(dto => dto.MobileNumber, dto => dto.MapFrom(src => src.Phones.First(p => p.Type == PhoneType.Mobile).Number));

Categories

Resources