Here is my settings, after them goes error and description.
Interfaces:
public interface IUser
{
int Id { get; set; }
string Firstname { get; set; }
string Lastname { get; set; }
string Email { get; set; }
string Fullname { get; }
}
public interface IPost
{
int Id { get; set; }
String Description { get; set; }
DateTime CreatedDate { get; set; }
int? UserId { get; set; }
IUser User { get; set; }
ICollection<IComment> Comments { get; set; }
}
public interface IComment
{
int Id { get; set; }
string Description { get; set; }
DateTime CreatedDate { get; set; }
int? PostId { get; set; }
int? UserId { get; set; }
IUser User { get; set; }
}
AutoMapper config:
Mapper.Initialize(cfg => cfg.CreateMap<IUser, DAL.User>());
Mapper.Initialize(cfg => cfg.CreateMap<IComment, DAL.Comment>());
Mapper.Initialize(cfg => cfg.CreateMap<IPost, DAL.Post>()
.ForMember(d => d.User, m => m.MapFrom(s => s.User))
.ForMember(d => d.UserId, m => m.MapFrom(s => s.User.Id))
.ForMember(d => d.Comments, m => m.MapFrom(s => s.Comments))
);
Mapper.AssertConfigurationIsValid();
EF code:
public IEnumerable<IPost> GetPosts()
{
IQueryable<DAL.Post> query = dbSet.Posts.Include("Comments");
var result = Mapper.Map<IEnumerable<IPost>>(query);
return result;
}
Error:
{"The following property on HiThere.DAL.User cannot be mapped: \n\tUser \nAdd a custom mapping expression, ignore, add a custom resolver, or modify the destination type HiThere.DAL.User.\nContext:\n\tMapping to property User from HiThere.Model.Common.IUser to HiThere.DAL.User\nException of type 'AutoMapper.AutoMapperConfigurationException' was thrown."}
Description:
I'm using AutoMapper inside ASP.NET WebAPI2 project to map data between DAL and Models. For some reason I cannot make proper mapping when using nested objects.
Any help will be appreciated.
Actually I fix it by adding all cfg.CreateMap to single Mapper.Inizialize. My mistake. Hopefully someone will find this usefull.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<DAL.Post, IPost>()
.ForMember(d => d.User, m => m.MapFrom(s => s.User))
.ForMember(d => d.UserId, m => m.MapFrom(s => s.User.Id))
.ForMember(d => d.Comments, m => m.MapFrom(s => s.Comments))
;
cfg.CreateMap<IUser, DAL.User>().ReverseMap();
cfg.CreateMap<IComment, DAL.Comment>().ReverseMap();
}
);
Mapper.AssertConfigurationIsValid();
Related
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.
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.
AssetAvailability
public class AssetAvailabilityDto
{
public int Id { get; set; }
public int AssetId { get; set; }
public int CalendarId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
Asset
public class AssetDto
{
public int Id { get; set; }
public int CategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public int OwnedBy { get; set; }
public DateTime LastModifiedAt { get; set; }
}
BookingSchedule
public class BookingScheduleDto
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
The destination to hold them all
public class AssetInformationDto
{
public int AssetId { get; set; }
public int CategoryId { get; set; }
public string AssetName { get; set; }
public string AssetDescription { get; set; }
public bool AssetIsActive { get; set; }
public int AssetOwnedBy { get; set; }
public DateTime AssetLastModifiedAt { get; set; }
// End of Assets
public int AvailabilityId { get; set; }
public DateTime AvailabilityStartDate { get; set; }
public DateTime AvailabilityEndDate { get; set; }
// End of Availability
public List<DateTime> BookingStartDate { get; set; } = new List<DateTime>();
public List<DateTime> BookingEndDate { get; set; } = new List<DateTime>();
// End of Booking schedule
}
Where the mapping takes place
AssetInformationDto information = new AssetInformationDto();
AssetDto asset = _assetService.GetAssetById(id);
AssetAvailabilityDto assetAvailability = _assetService.GetAssetAvailabilityByAssetId(id);
IEnumerable<BookingScheduleDto> schedule = _assetService.GetAssetBookingSchedule(id, true);
information = _mapper.Map<AssetInformationDto>(asset);
information = _mapper.Map<AssetInformationDto>(assetAvailability);
information = _mapper.Map<AssetInformationDto>(schedule);
The configuration of AutoMapper
CreateMap<AssetDto, AssetInformationDto>()
.ForMember(dest => dest.AssetName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.AssetDescription, opt => opt.MapFrom(src => src.Description))
.ForMember(dest => dest.AssetLastModifiedAt, opt => opt.MapFrom(src => src.LastModifiedAt))
.ForMember(dest => dest.AssetIsActive, opt => opt.MapFrom(src => src.IsActive))
.ForMember(dest => dest.AssetOwnedBy, opt => opt.MapFrom(src => src.OwnedBy))
.ForMember(dest => dest.AssetId, opt => opt.MapFrom(src => src.Id))
.ForAllOtherMembers(m => m.Ignore());
CreateMap<AssetAvailabilityDto, AssetInformationDto>()
.ForMember(dest => dest.AvailabilityStartDate, opt => opt.MapFrom(src => src.StartDate))
.ForMember(dest => dest.AvailabilityEndDate, opt => opt.MapFrom(src => src.EndDate))
.ForMember(dest => dest.AvailabilityId, opt => opt.MapFrom(src => src.Id))
.ForAllOtherMembers(m => m.Ignore());
CreateMap<IEnumerable<BookingScheduleDto>, AssetInformationDto>().AfterMap((s, d) =>
{
foreach (var item in s)
{
d.BookingStartDate.Add(item.StartDate);
d.BookingEndDate.Add(item.EndDate);
}
}).ForAllOtherMembers(m => m.Ignore());
This is being done on an ASP.NET Core 2.2 project, hence why AutoMapper is always injected with dependency injection (_mapper is the injected object)
The post shared here https://stackoverflow.com/a/38257912/11272124 is a solution to the above problem, by making a function "EntityMapper" and by slightly modifying it to accept the injected _mapper object, but I'd like to learn an alternative solution.
Isn't this possible with simply configuring AutoMapper to do so?
Maybe the reason for it not to work, is due to my poorly written out configuration.
The problem here is that after each mapped object, (After every information = ...) the previously assigned properties are reverted to their type's default values. That's why I included ForAllOtherMembers.Ignore() in the respective class where I configure how the mapping is done.
What I've been looking for was the usage of Mapper.Map(TSource, TDestination) (Mapper in my case would be _mapper, due to dependency injection.)
It executes a mapping from the source object to the existing destination object.
The method I previously used (Mapper.Map<TDestination>(TSource)) executes a mapping from the source object to a new destination object.
The code before:
AssetInformationDto information = new AssetInformationDto();
AssetDto asset = _assetService.GetAssetById(id);
AssetAvailabilityDto assetAvailability = _assetService.GetAssetAvailabilityByAssetId(id);
IEnumerable<BookingScheduleDto> schedule = _assetService.GetAssetBookingSchedule(id, true);
information = _mapper.Map<AssetInformationDto>(asset);
information = _mapper.Map<AssetInformationDto>(assetAvailability);
information = _mapper.Map<AssetInformationDto>(schedule);
The code that fixed my issue
AssetInformationDto information;
AssetDto asset = _assetService.GetAssetById(id);
AssetAvailabilityDto assetAvailability =
_assetService.GetAssetAvailabilityByAssetId(id);
IEnumerable<BookingScheduleDto> schedule =
_assetService.GetAssetBookingSchedule(id, true);
information = _mapper.Map<AssetInformationDto>(asset);
_mapper.Map(assetAvailability, information);
_mapper.Map(schedule, information);
I want to use automapper to flatten a list of entity heirarchies returned back from Entity Framework Core.
Here are my entities:
public class Employee {
public int Id { get; set; }
[Required]
public string Name { get; set; }
public double? PayRateRegular { get; set; }
public double? PayRateLoadedRegular { get; set; }
public double? GMOutput { get; set; }
public string EmployeeType { get; set; }
//List of CommissionDetails where this employee is the consultant
public IEnumerable<CommissionDetail> CommissionDetailConsultants { get; set; } = new List<CommissionDetail>();
}
public class Project {
public int Id { get; set; }
public string Description { get; set; }
public double? BillRateRegular { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public Customer Customer { get; set; }
public int CustomerId { get; set; }
}
public class Customer {
public int Id { get; set; }
public string Name { get; set; }
}
public class CommissionDetail {
public string SaleType { get; set; }
public double CommissionPercent { get; set; }
public bool? IsReported { get; set; }
public int? Level { get; set; }
public string BasedOn { get; set; }
public Project Project { get; set; }
public int ProjectId { get; set; }
public Employee SalesPerson { get; set; }
public int SalesPersonEmployeeId { get; set; }
public Employee Consultant { get; set; }
public int ConsultantEmployeeId { get; set; }
}
Here is my DTO:
public class ConsultantGridViewModel
{
public string ConsultantName { get; set; }
public string CustomerName { get; set; }
public string SalesPersonName { get; set; }
public string ProjectDescription { get; set; }
public double? PayRate { get; set; }
public double? LoadedRated { get; set; }
public double? BillRate { get; set; }
public double? GM { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public double CommissionPercent { get; set; }
public int? CommissionLevel { get; set; }
}
Here is my call to EF:
return await _dbContext.Employee
.AsNoTracking()
.Include(e => e.CommissionDetailConsultants)
.ThenInclude(cd => cd.SalesPerson)
.Include(e => e.CommissionDetailConsultants)
.ThenInclude(cd => cd.Project)
.ThenInclude(p => p.Customer)
.Where(e => e.EmployeeType == "Contractor")
.ToListAsync();
I'm currently flattening it with SelectMany as follows:
var consultants = employees.SelectMany(e =>
e.CommissionDetailConsultants,
(emp, com) => new ConsultantGridViewModel {
ConsultantName = emp.Name,
PayRate = emp.PayRateRegular,
LoadedRated = emp.PayRateLoadedRegular,
GM = emp.GMOutput,
BillRate = com.Project.BillRateRegular,
ProjectDescription = com.Project.Description,
ProjectStartDate = com.Project.StartDate,
ProjectEndDate = com.Project.EndDate,
CustomerName = com.Project.Customer.Name,
SalesPersonName = com.SalesPerson.Name,
CommissionPercent = com.CommissionPercent,
CommissionLevel = com.Level
});
I would like to use automapper instead. I've used automapper for all my other DTO mappings but I can't figure out how to use it to flatten a nested object like this.
Let rewrite what you have currently with SelectMany + Select utilizing the Consultant navigation property:
var consultants = employees
.SelectMany(e => e.CommissionDetailConsultants)
.Select(com => new ConsultantGridViewModel
{
ConsultantName = com.Consultant.Name,
PayRate = com.Consultant.PayRateRegular,
LoadedRated = com.Consultant.PayRateLoadedRegular,
GM = com.Consultant.GMOutput,
BillRate = com.Project.BillRateRegular,
ProjectDescription = com.Project.Description,
ProjectStartDate = com.Project.StartDate,
ProjectEndDate = com.Project.EndDate,
CustomerName = com.Project.Customer.Name,
SalesPersonName = com.SalesPerson.Name,
CommissionPercent = com.CommissionPercent,
CommissionLevel = com.Level
});
Now it can be seen that the CommissionDetail contains all the necessary data, so while you can't avoid SelectMany, you can replace the Select by creating a mapping from CommissionDetail to ConsultantGridViewModel and use something like this:
var consultants = Mapper.Map<List<ConsultantGridViewModel>>(
employees.SelectMany(e => e.CommissionDetailConsultants));
or even better, project directly to the DTO:
var consultants = await _dbContext.Employee
.Where(e => e.EmployeeType == "Contractor")
.SelectMany(e => e.CommissionDetailConsultants)
.ProjectTo<ConsultantGridViewModel>()
.ToListAsync();
Now the mapping.
AutoMapper will map automatically members like CommisionPercent. Also the Flattening feature will handle automatically mappings like Project.EndDate -> ProjectEndDate, Consultant.Name -> ConsultantName etc.
So as usual with AutoMapper you should specify manually the mapping of properties which don't fall into previous categories. The minimal configuration in this case would be something like this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<CommissionDetail, ConsultantGridViewModel>()
.ForMember(dst => dst.PayRate, opt => opt.MapFrom(src => src.Consultant.PayRateRegular))
.ForMember(dst => dst.LoadedRated, opt => opt.MapFrom(src => src.Consultant.PayRateLoadedRegular))
.ForMember(dst => dst.GM, opt => opt.MapFrom(src => src.Consultant.GMOutput))
.ForMember(dst => dst.BillRate, opt => opt.MapFrom(src => src.Project.BillRateRegular))
.ForMember(dst => dst.CustomerName, opt => opt.MapFrom(src => src.Project.Customer.Name))
.ForMember(dst => dst.CommissionLevel, opt => opt.MapFrom(src => src.Level));
});
P.S. You can even avoid SelectMany by basing your queries directly on CommissionDetail entity, for instance
var consultants = await _dbContext.Set<CommissionDetail>()
.Where(c => c.Consultant.EmployeeType == "Contractor")
.ProjectTo<ConsultantGridViewModel>()
.ToListAsync();
Note that when you do direct projection, there is no need of AsNoTracking or Include / ThenInclude.
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));