Automapper many to many mapping in 3-Tier architecture - c#

I have two Entities model on DataAccessLayer:
Consumer
public class Consumer
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public virtual ICollection<Contractor> Contractors { get; set; }
public Consumer()
{
Contractors = new List<Contractor>();
}
}
Contractor
public class Contractor
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public virtual ICollection<Consumer> Consumers { get; set; }
public Contractor()
{
Consumers = new List<Consumer>();
}
}
...and two class on Transfer Data Layer:
ConsumerTransfer
public class ConsumerTransfer
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public IList<ContractorTransfer> ContractorTransfer { get; set; }
}
ContractorTransfer
public class ContractorTransfer
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
}
**
How can I get data from Consumer to ConsumerTransfer?
**
I tried to do it this way
public IEnumerable<ConsumerTransfer> GetСonsumers()
{
Mapper.Initialize(cfg => cfg.CreateMap<Consumer, ConsumerTransfer>()
.ForMember(dto=>dto.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dto => dto.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dto => dto.PhoneNumber, opt => opt.MapFrom(src => src.PhoneNumber))
.ForMember(dto => dto.ContractorTransfer, opt => opt.MapFrom(src => src.Contractors))
);
return Mapper.Map<IEnumerable<Consumer>, IEnumerable<ConsumerTransfer>>(Database.Consumers.GetAll());
}

First of all, I'd rename your "ContractorTransfer" property to "Contractors". Generally I keep property names the same between DTOs and source types. Not a 100% rule, but unless I have a REALLY good reason on the client side (serialization concerns, whatever), then I don't change property names.
Second, your AutoMapper config is in the wrong place. You need to put the Initialize in the startup of your application, not right next to your mapping.
Third, your configuration is way too verbose. AutoMapper automatically maps properties with the same name.
Fourth, you're missing a map for the second set of source/destination types. Assuming you've fixed your inconsistent property name, here's your config:
Mapper.Initialize(cfg => {
cfg.CreateMap<Consumer, ConsumerTransfer>();
cfg.CreateMap<Contract, ContractTransfer>();
});
Then later on when you execute the map:
Mapper.Map<Consumer, ConsumerTransfer>(consumer);
That's it.

public IEnumerable<ConsumerTransfer> GetСonsumers()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Consumer, ConsumerTransfer>()
.ForMember(dto => dto.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dto => dto.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dto => dto.PhoneNumber, opt => opt.MapFrom(src => src.PhoneNumber))
.ForMember(dto => dto.ContractorTransfer, opt => opt.MapFrom(src => src.Contractors));
cfg.CreateMap<Contractor, ContractorTransfer>();
});
return Mapper.Map<IEnumerable<ConsumerTransfer>>(Database.Consumers.GetAll());
}

Related

How to map one source to multiple destinations using Automapper?

Here is the example:
Entities:
public class Contact
{
public int Id { get; set; }
......
public ICollection<ContactDetail> ContactDetails { get; set; }
}
public class ContactDetail
{
public Guid Id { get; set; }
public int ContactId { get; set; }
public ContactDetailTypes ContactDetailType { get; private set; }
public string Email { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
......
}
public enum ContactDetailTypes : byte
{
Email = 1,
Phone = 2,
Address = 3,
......
}
ViewModels:
public class ContactViewModel
{
public int Id { get; set; }
......
public ICollection<ContactEmailViewModel> ContactEmails { get; set; }
public ICollection<ContactPhoneViewModel> ContactPhones { get; set; }
public ICollection<ContactAddressViewModel> ContactAddresses { get; set; }
}
public class ContactEmailViewModel
{
public Guid Id { get; set; }
public int ContactId { get; set; }
public string Email { get; set; }
}
public class ContactPhoneViewModel
{
public Guid Id { get; set; }
public int ContactId { get; set; }
public string Phone { get; set; }
}
public class ContactAddressViewModel
{
public Guid Id { get; set; }
public int ContactId { get; set; }
public string Address { get; set; }
}
I want to map ContactDetail to ContactEmailViewModel, ContactPhoneViewModel, ContactAddressViewModel:
CreateMap<ContactDetail, ContactEmailViewModel>()
.ForMember(dest => dest.Id, opts =>
{
opts.PreCondition(s => s.ContactDetailType == ContactDetailTypes.Email);
opts.MapFrom(src => src.Id);
})
.ForMember(dest => dest.ContactId, opts =>
{
opts.PreCondition(s => s.ContactDetailType == ContactDetailTypes.Email);
opts.MapFrom(src => src.ContactId);
})
.ForMember(dest => dest.Email, opts =>
{
opts.PreCondition(s => s.ContactDetailType == ContactDetailTypes.Email);
opts.MapFrom(src => src.Email);
});
(Phone, Address map are similar to above)
When I map Contact to ContactViewModel with the above profiles, the count of ContactEmails, ContactPhones, ContactAddresses are always 0. I thought automapper can check the detailType and map source to different destinations but failed.
If I create a ContactDetailViewModel with all the properties of those three view model, it is mapping all properties from ContactDetail to ContactDetailViewModel though.
How to achieve mapping one source to multiple destinations using Automapper in this example?
Update:
I added a new map still no luck:
CreateMap<ICollection<ContactDetail>, ContactViewModel>()
.ForMember(dest => dest.ContactEmails, opts => opts.MapFrom(src => src.Where(s => s.ContactDetailType == ContactDetailTypes.Email)))
.ForMember(dest => dest.ContactPhones, opts => opts.MapFrom(src => src.Where(s => s.ContactDetailType == ContactDetailTypes.Phone)))
.ForMember(dest => dest.ContactAddresses, opts => opts.MapFrom(src => src.Where(s => s.ContactDetailType == ContactDetailTypes.Address)));
Just why did my post get devoted... I ask because I really couldn't find the answer after several attempts...
Finally make it work as follows:
CreateMap<Contact, ContactViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id))
...
.ForMember(dest => dest.ContactEmails, opts => opts.MapFrom(src => src.ContactDetails.Where(s => s.ContactDetailType == ContactDetailTypes.Email)))
.ForMember(dest => dest.ContactPhones, opts => opts.MapFrom(src => src.ContactDetails.Where(s => s.ContactDetailType == ContactDetailTypes.Phone)))
.ForMember(dest => dest.ContactAddresses, opts => opts.MapFrom(src => src.ContactDetails.Where(s => s.ContactDetailType == ContactDetailTypes.Address)));

Automapper map list of object to another object in different list of objects, nested mapping

public class First
{
public List<ClassA> AList{ get; set; }
}
public class ClassA
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
public class Second
{
public List<ClassB> BList{ get; set; }
}
public class ClassB
{
public ClassC Cobj { get; set; }
public string Email { get; set; }
}
public class ClassC
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
unable to get results for converting ClassA to ClassC, while trying to map First and Second
I think the Reverse Mapping and Unflattening could help
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<First, Second>()
.ForMember(dest => dest.BList, opt => opt.MapFrom(src => src.AList));
cfg.CreateMap<ClassB, ClassA>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.Cobj.FirstName))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.Cobj.LastName))
.ReverseMap();
});

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.

How to map a single object from multiple ones, using AutoMapper?

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

Automapper complex types mapping exception

I'm trying to implement the AutoMapper for a new module.
I have the MVC model at the web site, I'm working on, and it looks like this:
public class MvcModel
{
public Params Params { get; set; }
public Steps Steps { get; set; }
}
public class Params
{
public int? RequestId { get; set; }
public bool NewClient { get; set; }
}
public class Steps
{
public Step1 Step1 { get; set; }
public Step2 Step2 { get; set; }
public Step3 Step3 { get; set; }
}
public class Step1
{
public int Name { get; set; }
}
public class Step2
{
public int Phone { get; set; }
}
public class Step3
{
public int Email { get; set; }
}
For the other hand I have next class, that I have to pass to some service:
public class Request
{
public Parameters Parameters { get; set; }
public RequestContent RequestContent { get; set; }
}
public class Parameters
{
public int NewClient { get; set; }
}
public class RequestContent
{
public int Id { get; set; }
public int InnerId { get; set; }
public string Session { get; set; }
public string Clerk { get; set; }
public bool Private { get; set; }
public PersonalDetails PersonalDetails { get; set; }
public Phones Phones { get; set; }
public ElectonicCommunication ElectonicCommunication { get; set; }
}
public class PersonalDetails
{
public int Name { get; set; }
}
public class Phones
{
public int Phone { get; set; }
}
public class ElectonicCommunication
{
public int Email { get; set; }
}
I've created a new AutoMapper configuration class (that is called from Global.asax):
public class AutoMapperConfig
{
public static void Configure()
{
MapperConfiguration MapperConfiguration = new MapperConfiguration(cfg => {
cfg.AddProfile<Out>();
cfg.AddProfile<In>();
cfg.CreateMap<MvcModel, Request>();
});
MapperConfiguration.AssertConfigurationIsValid();
}
}
public class Out: Profile
{
protected override void Configure()
{
CreateMap<MvcModel, Request>()
.ForMember(dest => dest.Parameters, opt => opt.MapFrom(src => src.Params))
.ForMember(dest => dest.RequestContent, opt => opt.MapFrom(src => src.Steps));
}
}
public class In: Profile
{
protected override void Configure()
{
CreateMap<Request, MvcModel>()
.ForMember(dest => dest.Params, opt => opt.MapFrom(src => src.Parameters))
.ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.RequestContent));
}
}
Those are schematically similar, though the real object are a bit bigger and have different names.
Excpectations: MvcModel to be mapped to Request and backwards.
To be more accurate:
MvcModel.Params to be mapped to Request.Parameters and backwards
MvcModel.Steps to be mapped to Request.RequestContent and backwards
InnerId, Session, Clerk, Private from Request.RequestContent must be ignored
The problem: I'm getting an error, when on application start:
The following property on ... cannot be mapped:
Add a custom mapping expression, ignore, add a custom resolver, or modify
the destination type ...
Context:
Mapping to property ... from ... to ...
Mapping from type ... to ...
from source type AutoMapperMessageBugTests.SourceChild Mapping to type
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
I didn't find good documentation on how to use AutoMapper globally for the application, and just can't figure how to use it properly and what exactly I'm doing wrong.
I couldn't find a good example of complex objects mapping ether...
Can someone help me with this?
Thank's to stuartd this are working now!
The thing that I didn't understand was, that I have to map bottom to top! All the sub-objects have to mapped first, so they would be recognized at the moment parent objects are mapped!
Now profile's looks like this:
public class Out: Profile
{
protected override void Configure()
{
CreateMap<Step1, PersonalDetails>();
CreateMap<Step2, Phones>();
CreateMap<Step3, ElectonicCommunication>();
CreateMap<Params, Parameters>();
CreateMap<Params, RequestContent>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.RequestId))
.ForAllMembers(opt => opt.Ignore());
CreateMap<Steps, RequestContent>()
.ForMember(dest => dest.PersonalDetails, opt => opt.MapFrom(src => src.Step1))
.ForMember(dest => dest.Phones, opt => opt.MapFrom(src => src.Step2))
.ForMember(dest => dest.ElectonicCommunication, opt => opt.MapFrom(src => src.Step3))
.ForAllMembers(opt => opt.Ignore());
CreateMap<MvcModel, Request>()
.ForMember(dest => dest.Parameters, opt => opt.MapFrom(src => src.Params))
.ForMember(dest => dest.RequestContent, opt => opt.MapFrom(src => src.Steps));
}
}

Categories

Resources