How to map one source to multiple destinations using Automapper? - c#

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

Related

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

Navigation mapping issues in EF Core

I'm trying to achieve the following layout:
User table (has link to user details)
User details table (holds links to various detail tables)
but am getting the following error:
System.InvalidOperationException: Cannot create a relationship between 'Address.ClientDetails' and 'ClientDetails.ResidentialAddress', because there already is a relationship between 'ClientDetails.PostalAddress' and 'Address.ClientDetails'. Navigation properties can only participate in a single relationship.
I understand this problem would occur if entity framework had no way to identify which address to link to each address - but i thought i take care of that by specifying 2 links in the model and then each key map in the mapping class. Any help would be great!
my Client model and mapping looks as follows:
public class Client : BaseEntity
{
public ClientDetails ApplicantDetails
{
get
{
return this.ClientDetails.SingleOrDefault(e => e.ClientType == Enums.ClientType.Applicant.ToString());
}
}
public ClientDetails SpouseDetails
{
get
{
return this.ClientDetails.SingleOrDefault(e => e.ClientType == Enums.ClientType.Spouse.ToString());
}
}
public ICollection<ClientDetails> ClientDetails { get; set; }
public ICollection<BankDetails> BankDetails { get; set; }
public ICollection<Expenses> Expenses { get; set; }
public ICollection<Obligation> Obligations { get; set; }
public ICollection<Budget> Budgets { get; set; }
public ICollection<Document.Document> Documents { get; set; }
public virtual Workflow.Workflow Workflow { get; set; }
Mapping
public class ClientMapping: IEntityTypeConfiguration<Entities.Client.Client>
{
public void Configure(EntityTypeBuilder<Entities.Client.Client> builder)
{
builder.ToTable("Client");
builder.HasKey(e => e.Id);
builder.HasMany(e => e.ClientDetails).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.Documents).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasOne(e => e.Workflow).WithOne(e => e.Client).HasForeignKey<Entities.Workflow.Workflow>(e => e.ClientId);
builder.HasMany(e => e.Obligations).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.Expenses).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.Budgets).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.BankDetails).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.Ignore(e => e.ApplicantDetails);
builder.Ignore(e => e.SpouseDetails);
}
}
Client Details and mapping
public class ClientDetails
{
public int ClientId { get; set; }
public int PersonalDetailsId { get; set; }
public int EmployerId { get; set; }
public int ResidentialAddressId { get; set; }
public int PostalAddressId { get; set; }
public int IncomeId { get; set; }
public string ClientType { get; set; }
public virtual Client Client { get; set; }
public virtual PersonalDetails PersonalDetails { get; set; }
public virtual Employer Employer { get; set; }
public virtual Address ResidentialAddress { get; set; }
public virtual Address PostalAddress { get; set; }
public virtual Income Income { get; set; }
}
mapping
public class ClientDetailsMapping : IEntityTypeConfiguration<Entities.Client.ClientDetails>
{
public void Configure(EntityTypeBuilder<ClientDetails> builder)
{
builder.ToTable("ClientDetails");
builder.HasKey(e => new { e.IncomeId, e.PersonalDetailsId, e.ClientId, e.PostalAddressId, e.ResidentialAddressId } );
builder.HasOne(e => e.Income).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.IncomeId);
builder.HasOne(e => e.PostalAddress).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.PostalAddressId);
builder.HasOne(e => e.ResidentialAddress).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.ResidentialAddressId);
builder.HasOne(e => e.Employer).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.EmployerId);
builder.HasOne(e => e.PersonalDetails).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.PersonalDetailsId);
}
}
Can you try to delete the content of "WithOne"?
try this:
builder.HasOne(e => e.PostalAddress).WithOne().HasForeignKey<ClientDetails>(e => e.PostalAddressId);
builder.HasOne(e => e.ResidentialAddress).WithOne().HasForeignKey<ClientDetails>(e => e.ResidentialAddressId);
Found this post:
ef core - two one to one on one principal key
Ended up implementing solution 3, client details now has a collection of addresses (which have an address type linked), I then added an address getter on the client details to get the address I want at a later time. Everything seems to work correctly now.

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.

Automapper many to many mapping in 3-Tier architecture

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

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