AutoMapper - Mapping child properties - c#

I have a view model that looked like this:
class PersonSectionViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class PersonViewModel
{
public PersonSectionViewModel Main { get; set; }
public PersonSectionViewModel Partner { get; set; }
}
And I tried mapping the properties like this, but it's not working at all:
class PersonDTO
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FirstName_2 { get; set; }
public string LastName_2 { get; set; }
}
CreateMap<PersonDTO, PersonViewModel>()
.ForMember(dest => dest.Main.FirstName,
opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.Main.LastName,
opt => opt.MapFrom(src => src.LastName))
.ForMember(dest => dest.Partner.FirstName,
opt => opt.MapFrom(src => src.FirstName_2))
.ForMember(dest => dest.Partner.LastName,
opt => opt.MapFrom(src => src.LastName_2))
What's the right way of mapping this?
Thank you.

Working with .ForPath().
CreateMap<PersonDTO, PersonViewModel>()
.ForPath(dest => dest.Main.FirstName,
opt => opt.MapFrom(src => src.FirstName))
.ForPath(dest => dest.Main.LastName,
opt => opt.MapFrom(src => src.LastName))
.ForPath(dest => dest.Partner.FirstName,
opt => opt.MapFrom(src => src.FirstName_2))
.ForPath(dest => dest.Partner.LastName,
opt => opt.MapFrom(src => src.LastName_2));
Demo # .NET Fiddle
Reference
Use of .ForPath() | GitHub

Related

Stackoverflow exception automapper

So a little insight into my problem, i have 4 classes employee, employeedto, request and requestdto as shown below, they have circular references, i have created mapping configuration for them but when i try to map list of employees to employeedtos i get stackoverflow exception even though i am using MaxDepth(1).
public class Employee
{
public string Name { get; set; }
public List<Request> Requests { get; set; }
}
public class EmployeeDto
{
public string Name { get; set; }
public List<RequestDto> RequestsDto { get; set; }
}
public class Request
{
public int Id { get; set; }
public Employee CreatedBy { get; set; }
}
public class RequestDto
{
public int Id { get; set; }
public EmployeeDto CreatedBy { get; set; }
}
The mapping configuration:
cfg.CreateMap<Employee, EmployeeDto>().MaxDepth(1)
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.AfterMap((src, dest, context) =>
{
dest.RequestsDto = context.Mapper.Map<List<RequestDto>>(src.Requests);
});
cfg.CreateMap<EmployeeDto, Employee>().MaxDepth(1)
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.AfterMap((src, dest, context) =>
{
dest.Requests = context.Mapper.Map<List<Request>>(src.RequestsDto);
});
cfg.CreateMap<Request, RequestDto>().MaxDepth(1)
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.AfterMap((src, dest, context) =>
{
dest.CreatedBy = context.Mapper.Map<EmployeeDto>(src.CreatedBy);
});
cfg.CreateMap<RequestDto, Request>().MaxDepth(1)
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.AfterMap((src, dest, context) =>
{
dest.CreatedBy = context.Mapper.Map<Employee>(src.CreatedBy);
});
The exception occurs on this line when i try to map employees to employedtos.
var mapped = _mapper.Map<List<EmployeeDto>>(employees);

Automapper child collection loses foreign key id while updating

I am trying to map entity and it's child collection. First I retrieve entity and child collection from database:
public async Task<int> Update(EmailTemplateModel model, int id)
{
var emailTemplate = _context.EmailTemplates.Include("EmailTemplateComponents").Include("Site").FirstOrDefault(x => x.Id == id);
_mapper.Map(model, emailTemplate);
return await _context.SaveChangesAsync();
}
then I use mapper, but in this case I lose EmailTemplate link in EmailTemplateComponents and I get an error:
"$id": "1",
"Message": "An error has occurred.",
"ExceptionMessage": "The operation failed: The relationship could not be changed because one or more of the foreign-key properties is
non-nullable. When a change is made to a relationship, the related
foreign-key property is set to a null value. If the foreign-key does
not support null values, a new relationship must be defined, the
foreign-key property must be assigned another non-null value, or the
unrelated object must be deleted.",
"ExceptionType": "System.InvalidOperationException",
My entities are this:
[Serializable]
public class EmailTemplate : EntityBase
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public string AuthorDisplayName { get; set; }
public int UserId { get; set; }
public bool IsDeleted { get; set; }
public virtual List<EmailTemplateComponent> EmailTemplateComponents { get; set; }
//Site
public int SiteId { get; set; }
[ForeignKey("SiteId")]
public virtual Site Site { get; set; }
}
[Serializable]
public class EmailTemplateComponent : EntityBase
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool IsTop { get; set; }
public bool IsBottom { get; set; }
public bool AlignRight { get; set; }
public string Text { get; set; }
public string Title { get; set; }
public string Url { get; set; }
public string ImageUrl { get; set; }
//Email template
public int EmailTemplateId { get; set; }
[ForeignKey("EmailTemplateId")]
public virtual EmailTemplate EmailTemplate { get; set; }
}
And mapping is this:
public class EmailTemplateProfile : Profile
{
protected override void Configure()
{
CreateMap<EmailTemplateModel, EmailTemplate>()
.ForMember(dest => dest.AuthorDisplayName, opts => opts.MapFrom(src => src.Author))
.ForMember(dest => dest.EmailTemplateComponents, opts => opts.MapFrom(src => src.Components))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.Title))
.IgnoreAllNonExisting()
.IgnoreAllNonExistingSource();
CreateMap<EmailTemplate, EmailTemplateModel> ()
.ForMember(dest => dest.Author, opts => opts.MapFrom(src => src.AuthorDisplayName))
.ForMember(dest => dest.Components, opts => opts.MapFrom(src => src.EmailTemplateComponents))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.Title))
.IgnoreAllNonExisting()
.IgnoreAllNonExistingSource();
CreateMap<EmailTemplateComponent, EmailTemplateComponentModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id))
.ForMember(dest => dest.EmailTemplateId, opts => opts.MapFrom(src => src.EmailTemplateId))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name))
.ForMember(dest => dest.IsTop, opts => opts.MapFrom(src => src.IsTop))
.ForMember(dest => dest.IsBottom, opts => opts.MapFrom(src => src.IsBottom))
.ForMember(dest => dest.Text, opts => opts.MapFrom(src => src.Text))
.ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.Title))
.F
orMember(dest => dest.Url, opts => opts.MapFrom(src => src.Url))
.ForMember(dest => dest.ImageUrl, opts => opts.MapFrom(src => src.ImageUrl))
.ForMember(dest => dest.AlignRight, opts => opts.MapFrom(src => src.AlignRight))
.ForMember(dest => dest.EmailTemplate, opts => opts.UseDestinationValue())
.IgnoreAllNonExisting()
.IgnoreAllNonExistingSource()
.ReverseMap();
}
I can't seem to figure out why it is replacing the values in child collection with nulls?

How can i map a child object with AutoMapper?

I am not sure if i am over thinking this or not, but i cannot sus this out.
I have a parent object here called Template
public Template()
{
public long Id { get; set; }
public Scoring SubProperty { get; set; }
}
Here is my Scoring object which is a child property of Template
public enum MyEnum : short
{
Basic = 0
}
public Scoring()
{
public MyEnum Type { get; set; }
public string Text { get; set; }
}
I have a TemplateModel defined, like so, which i want to convert to
public TemplateModel()
{
public long Id { get; set; }
public string Type { get; set; }
public string Text { get; set; }
}
In my AutoMapper Profile, i have set this up like so, to covert Template to TemplateModel.
public class TemplateProfile : Profile
{
protected override void Configure()
{
// converters
this.CreateMap<TemplateType, string>().ConvertUsing(new TemplateTypeConverter());
// models
this.CreateMap<Template, TemplateModel>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Scoring.Type))
.ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Scoring.Text));
}
/// <summary>
/// Convert TemplateType to string
/// </summary>
private class TemplateTypeConverter : ITypeConverter<TemplateType, string>
{
public string Convert(ResolutionContext context)
{
return context.SourceValue.ToString().ToLower();
}
}
}
How can i convert TemplateModel back into Template?
If i add the following, i get an exception, because dest.Scoring.Type is not a root property.
this.CreateMap<TemplateModel, Template>()
.ForMember(dest => dest.Scoring.Type, opt => opt.MapFrom(src => src.Type))
.ForMember(dest => dest.Scoring.Text, opt => opt.MapFrom(src => src.Text));
Any help much appreciated. In this case a Template must always have a Scoring object, but in other cases i have optional properties. If someone could help me with both that would be great.
this.CreateMap<TemplateModel, Template>()
.ForMember(dest => dest.SubProperty, opt => opt.MapFrom(src => src));
this.CreateMap<TemplateModel, Scoring>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type))
.ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Text));

Automapper - ReverseMap() does not perform mapping

I have the following 2 classes:
public class ReferenceEngine
{
public Guid ReferenceEngineId { get; set; }
public string Description { get; set; }
public int Horsepower { get; set; }
}
public class Engine
{
public Guid Id { get; set; }
public string Description { get; set; }
public int Power { get; set; }
}
I am using automapper to perform a mapping from ReferenceEngine to Engine and vice versa. Notice that the properties ReferenceEngineId/Id and Horsepower/Power does not have the same name.
The following mapping configuration works and the properties having different names are successfully mapped:
public static void ConfigureMapperWorking()
{
AutoMapper.Mapper.CreateMap<ReferenceEngine, Engine>()
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description)).ReverseMap();
AutoMapper.Mapper.CreateMap<ReferenceEngine, Engine>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => Guid.Parse(src.ReferenceEngineId.ToString())))
.ForMember(dest => dest.Power, opt => opt.MapFrom(src => src.Horsepower));
AutoMapper.Mapper.CreateMap<Engine, ReferenceEngine>()
.ForMember(dest => dest.ReferenceEngineId, opt => opt.MapFrom(src => Guid.Parse(src.Id.ToString())))
.ForMember(dest => dest.Horsepower, opt => opt.MapFrom(src => src.Power));
}
However the following does not work although I invoke the method ReverseMap() at the end:
public static void ConfigureMapperNotWorking()
{
AutoMapper.Mapper.CreateMap<ReferenceEngine, Engine>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.ReferenceEngineId))
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description))
.ForMember(dest => dest.Power, opt => opt.MapFrom(src => src.Horsepower)).ReverseMap();
}
My question is, when property names are different, should we manually specify the TSource->TDestination and TDestination->TSource mapping? I thought the purpose of the ReverseMap is to avoid us from manually specifying the bi-directional mapping.
ReverseMap only creates a simple reverse mapping. For example it would automatically configure
Mapper.CreateMap<Engine, ReferenceEngine>();
from
Mapper.CreateMap<ReferenceEngine, Engine>();
To get anything more complex, you have to configure it manually.

Automapper complex objects

I've begun implementing this;
Automapper, mapping to a complex object but figured there must be a better way.
So I created this;
Mapper.CreateMap<StoreTransportWindow, CSVWindow>()
.ForMember(dest => dest.DC, opt => opt.ResolveUsing(fa => fa.DC.number))
.ForMember(dest => dest.Type, opt => opt.ResolveUsing(fa => fa.Type))
;
Mapper.CreateMap<Store, CSVStore>()
.ForMember(dest => dest.StoreName, opt => opt.ResolveUsing(fa => fa.name))
.ForMember(dest => dest.StoreNumber, opt => opt.ResolveUsing(fa => fa.number))
;
Now I'd like to use the above mappings in the primary map;
Mapper.CreateMap<Store, CSVLineObject>()
.ForMember( dest => dest.store, opt => opt.ResolveUsing(/* This is where I'd like to use the above Store to CSVStore mapping */))
;
Is this possible?
edit
public class CSVStore
{
public string StoreNumber { get; set; }
public string StoreName { get; set; }
}
public class CSVWindow
{
public string Type { get; set; }
public string DC { get; set; }
public string TPC { get; set; }
public class CSVLineObject
{
public CSVStore store { get; set; }
public List<CSVWindow> storeWindows { get; set; }
As mentioned in the comment, the initial mappings should probably be more like:
Mapper.CreateMap<StoreTransportWindow, CSVWindow>()
.ForMember(dest => dest.DC, opt => opt.MapFrom(src => src.DC.number));
// Mapping for property Type not required
Mapper.CreateMap<Store, CSVStore>()
.ForMember(dest => dest.StoreName, opt => opt.MapFrom(src => src.name))
.ForMember(dest => dest.StoreNumber, opt => opt.MapFrom(src => src.number));
Now say you have the following:
public class Source
{
public Store Store { get; set; }
}
public class Destination
{
public CSVStore Store { get; set; }
}
Then the following mapping will suffice (as you've already defined the nested mapping Store to CSVStore):
Mapper.CreateMap<Source, Destination>();
However if Destination was more like this:
public class Destination
{
public CSVStore CSVStore { get; set; }
}
Then you'll need to explicitly define the properties to be mapped:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.CSVStore, opt => opt.MapFrom(src => src.Store));
(Note that the mapping from Store to CVStore is applied automatically.)
If for some reason you do need to explicitly define a nested mapping, you can do something like this:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.destproperty,
opt => opt.MapFrom(
src => Mapper.Map<SrcType, DestType>(src.srcproperty));
I have needed to use that at times, but not very often as the default functionality takes care of it for you automatically.
I can provide more details if required if you can expand on your requirements.

Categories

Resources