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);
Related
i have come across a similar problem as mentioned in this question AutoMapper setting destination object to null
my mapper is creating an empty object of destination type although i can see values in my source object in debugger.
my source object is a grpc message of submitOrder type
syntax = "proto3";
option csharp_namespace = "SubmitOrderService";
package tracksubmitorder;
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
service SubmitOrderService {
rpc SubmitOrder(submitOrder) returns (google.protobuf.Empty);
}
// The request message containing the user's name.
message submitOrder {
int64 orderID= 1;
int32 beef =2;
int32 chicken =3;
int32 tofu =4;
int32 shrimp =5;
google.protobuf.Timestamp createdOn = 20;
string comment=30;
}
my destination is a data model
namespace SubmitOrderService.Models
{
public class Submit_Order
{
public long Id { get; set; }
public string Comments { get; set; }
public DateTime CreatedAt { get; set; }
public int CountBeef { get; set; }
public int CountChicken { get; set; }
public int CountTofu { get; set; }
public int CountShrimp { get; set; }
}
}
this is my profile file
using AutoMapper;
using SubmitOrderService.Models;
namespace SubmitOrderService
{
public class SubmitOrderProfile: Profile
{
public SubmitOrderProfile()
{
CreateMap<submitOrder, Submit_Order>().ForMember(
dest => dest.Comments,
opt => opt.MapFrom(src => $"{src.Comment}")
).ForMember(
dest => dest.CreatedAt,
opt => opt.MapFrom(src => $"{src.CreatedOn }")
)
.ForMember(
dest => dest.CountChicken ,
opt => opt.MapFrom(src => $"{src.Chicken}")
)
.ForMember(
dest => dest.CountBeef ,
opt => opt.MapFrom(src => $"{src.Beef}")
)
.ForMember(
dest => dest.CountShrimp ,
opt => opt.MapFrom(src => $"{src.Shrimp}")
)
.ForMember(
dest => dest.CountTofu ,
opt => opt.MapFrom(src => $"{src.Tofu}")
).ReverseMap();
}
}
}
this is my line for mapping where reqOrder is of type submitOrder
var order1 = _mapper.Map<Submit_Order>(reqOrder);
ps. This is my first time trying out Automapper, so any kind of help will be appreciated.
solved the problem. I had to explicitly convert google.protobuf.Timestamp to DateTime for my CreatedAt member. like this,
ForMember( dest => dest.CreatedAt, opt => opt.MapFrom(src => src.CreatedOn.ToDateTime()) )
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
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?
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));
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.