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