AutoMapper: Automapper 6.0.2.0 throws StackOverFlowException even with PreserveChanges() & MaxDepth(n)
#Hello everyone, I’ll get straight to the point:
With the 6.0.2.0 version, I’m getting StackOverFlow exception everytime I attempt to retrieve a mapping from Model to DTO.
I’ve tried with PreserveChanges() & MAxDepth(int) methods already but still, nothing achieved.
In the official documentation, there is no sample that explains how to handle Child entities to be mapped. Only what is mentioned below:
// Self-referential mapping
cfg.CreateMap<Category, CategoryDto>().MaxDepth(3);
// Circular references between users and groups
cfg.CreateMap<User, UserDto>().PreserveReferences();
Also in #1639, @lbargaoanu mentioned that we can use ForAllMaps to be set everywhere. But the problem, is that we are using profiles for this and well, I’m not to experienced enough in order to dig the whole idea about what is the solution being commented, my apologizes…
I know I should be missing something for sure, but my question here is “what?”…
Thanks in advance & Best Regards!!
Source types
public abstract class ControlData
{
public DateTime CreatedDate { get; set; }
public int CreatedById { get; set; }
[ForeignKey("CreatedById")]
public Collaborator CreatedBy { get; set; }
public DateTime? UpdatedDate { get; set; }
public int? UpdatedById { get; set; }
[ForeignKey("UpdatedById")]
public Collaborator UpdatedBy { get; set; }
}
[Table("MyOpportunityTable")]
public class Opportunity : ControlData
{
[Column("OpportunityId")]
[Key]
public int Id { get; set; }
[Column("OpportunityStatusId")]
public int StatusId { get; set; }
[ForeignKey("StatusId")]
public OpportunityStatus Status { get; set; }
public string ProjectId { get; set; }
[ForeignKey("ProjectId")]
public Project Project { get; set; }
public string MarketId { get; set; }
[ForeignKey("MarketId")]
public Market Market { get; set; }
public string CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
public string Name { get; set; }
[Column("OpportunityTypeId")]
public int TypeId { get; set; }
[ForeignKey("TypeId")]
public OpportunityType Type { get; set; }
[Column("OpportunityPriorityId")]
public int PriorityId { get; set; }
[ForeignKey("PriorityId")]
public OpportunityPriority Priority { get; set; }
public int? OpportunityCancellationReasonId { get; set; }
[ForeignKey("OpportunityCancellationReasonId")]
public OpportunityCancellationReason CancellationReason { get; set; }
public string CancellationComments { get; set; }
public int? CancellationById { get; set; }
[ForeignKey("CancellationById")]
public Collaborator CancellationBy { get; set; }
public DateTime? CancellationDate { get; set; }
public bool Active { get; set; }
public List<OpportunityRole> OpportunityRoles { get; set; }
public List<Position> Positions { get; set; }
}
[Table("MyPositionTable")]
public class Position : ControlData
{
[Key]
[Column("PositionId")]
public int Id { get; set; }
[Column("PositionStatusId")]
public int StatusId { get; set; }
[ForeignKey("StatusId")]
public PositionStatus Status { get; set; }
public int OpportunityId { get; set; }
[ForeignKey("OpportunityId")]
public Opportunity Opportunity { get; set; }
public int Total { get; set; }
[Column("PositionDurationId")]
public int DurationId { get; set; }
[ForeignKey("DurationId")]
public PositionDuration Duration { get; set; }
public DateTime StartDate { get; set; }
//TODO Agregar las otras propiedades con sus respectivos catalogos
public string PracticeId { get; set; }
[ForeignKey("PracticeId")]
public Practice Practice { get; set; }
public int RoleId { get; set; }
[ForeignKey("RoleId")]
public PersonRole Role { get; set; }
public int PlatformId { get; set; }
[ForeignKey("PlatformId")]
public Platform Platform { get; set; }
public int LevelId { get; set; }
[ForeignKey("LevelId")]
public Level Level { get; set; }
public int EnglishLevelId { get; set; }
[ForeignKey("EnglishLevelId")]
public EnglishLevel EnglishLevel { get; set; }
public string CountryId { get; set; }
public int LocationId { get; set; }
[ForeignKey("LocationId")]
public Location Location { get; set; }
public int? OfficeId { get; set; }
public int OperationId { get; set; }
[ForeignKey("OperationId")]
public Person Operation { get; set; }
public int? EvaluatorId { get; set; }
[ForeignKey("EvaluatorId")]
public Collaborator Evaluator { get; set; }
public int? SourcerId { get; set; }
[ForeignKey("SourcerId")]
public Collaborator Sourcer { get; set; }
public List<Candidate> Candidates { get; set; }
public int? PositionCancellationReasonId { get; set; }
[ForeignKey("PositionCancellationReasonId")]
public PositionCancellationReason CancellationReason { get; set; }
public string CancellationComments { get; set; }
public int? CancellationById { get; set; }
[ForeignKey("CancellationById")]
public Collaborator CancellationBy { get; set; }
public DateTime? CancellationDate { get; set; }
public bool Active { get; set; }
public bool WhereAvailable { get; set; }
public bool RequestAsset { get; set; }
public string CityZone { get; set; }
public string TravelsTo { get; set; }
public string Description { get; set; }
public string SpecificationFile { get; set; }
public int PositionPriorityId { get; set; }
public int? SourcingGroupId { get; set; }
Destination types
public abstract class ControlDataDTO
{
public DateTime CreatedDate { get; set; }
public int CreatedById { get; set; }
public CollaboratorPlainDTO CreatedBy { get; set; }
public DateTime? UpdatedDate { get; set; }
public int? UpdatedById { get; set; }
public CollaboratorPlainDTO UpdatedBy { get; set; }
}
public class OpportunityDTO: ControlDataDTO
{
public int Id { get; set; }
public int StatusId { get; set; }
public OpportunityStatusDTO Status { get; set; }
public string ProjectId { get; set; }
public ProjectDTO Project { get; set; }
public string MarketId { get; set; }
public MarketDTO Market { get; set; }
public string CustomerId { get; set; }
public CustomerDTO Customer { get; set; }
public string Name { get; set; }
public int TypeId { get; set; }
public OpportunityTypeDTO Type { get; set; }
public int PriorityId { get; set; }
public OpportunityPriorityDTO Priority { get; set; }
public int? OpportunityCancellationReasonId { get; set; }
public OpportunityCancellationReasonDTO CancellationReason { get; set; }
public string CancellationComments { get; set; }
public int? CancellationById { get; set; }
public CollaboratorPlainDTO CancellationBy { get; set; }
public DateTime? CancellationDate { get; set; }
public CollaboratorDTO Responsible { get; set; }
public List<OpportunityRoleDTO> OpportunityRoles { get; set; }
public int TotalPositions { get; set; }
public bool CandidatesWarning { get; set; }
public bool Active { get; set; }
public List<PositionDTO> Positions { get; set; }
}
public class PositionDTO: ControlDataDTO
{
public int Id { get; set; }
public int StatusId { get; set; }
public PositionStatusDTO Status { get; set; }
public int OpportunityId { get; set; }
public OpportunityDTO Opportunity { get; set; }
public int Total { get; set; }
public int DurationId { get; set; }
public PositionDurationDTO Duration { get; set; }
public DateTime StartDate { get; set; }
public string PracticeId { get; set; }
public PracticeDTO Practice { get; set; }
public int RoleId { get; set; }
public PersonRoleDTO Role { get; set; }
public int PlatformId { get; set; }
public PlatformDTO Platform { get; set; }
public int LevelId { get; set; }
public LevelDTO Level { get; set; }
public int EnglishLevelId { get; set; }
public EnglishLevelDTO EnglishLevel { get; set; }
public string CountryId { get; set; }
public int LocationId { get; set; }
public LocationDTO Location { get; set; }
public int? OfficeId { get; set; }
public int OperationId { get; set; }
public PersonDTO Operation { get; set; }
public string OperationIS { get; set; }
public bool WhereAvailable { get; set; }
public bool RequestAsset { get; set; }
public string CityZone { get; set; }
public string TravelsTo { get; set; }
public string Description { get; set; }
public int CandidatesAccepted { get; set; }
public int CandidatesRejected { get; set; }
public int CandidatesWaiting { get; set; }
public bool HasCandidatesWaiting { get; set; }
public int TotalCandidates { get; set; }
public string SpecificationFile { get; set; }
public int? EvaluatorId { get; set; }
public int? SourcerId { get; set; }
public CollaboratorDTO Sourcer { get; set; }
public int? SourcingGroupId { get; set; }
public PositionCancellationReasonDTO CancellationReason { get; set; }
}
Target class where I need to catch the expected DTO --> List<OpportunityDTO>
public class OpportunityLogic
{
public ActionResponse<List<OpportunityDTO>> GetOpportunitiesWithPositions(int personId)
{
var listOpportunities2 = new List<Opportunity>
{
new Opportunity
{
Id = 2,
Name = "Another Opportunity",
Active = true,
CreatedDate = new DateTime(2017, 7, 10),
Positions = new List<Position> {
new Position
{
Id= 1,
LocationId = 4
},
new Position
{
Id= 2,
LocationId = 4
},
new Position
{
Id= 3,
LocationId = 4
}
}
}
};
var mappedOpportunities = Mapper.Map<List<OpportunityDTO>>(listOpportunities);
return new ActionResponse<List<OpportunityDTO>> (mappedOpportunities);
}
}
Mapping configuration
public class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(cfg =>
{ cfg.AddProfile<OpportunityMappingProfile>(); }
}
}
public class OpportunityMappingProfile : Profile
{
public OpportunityMappingProfile()
{
CreateMap<Opportunity, OpportunityDTO>()
.ForMember(x => x.Responsible,
x => x.MapFrom(c => GetFromOpportunityRoles(c.OpportunityRoles, Constants.OpportunityResponsible)))
.ForMember(x => x.TotalPositions, x => x.MapFrom(c => c.Positions.Count()))
.ForMember(x => x.CandidatesWarning,
x => x.MapFrom(c =>
c.Positions.Count() > 0
? c.Positions.Any(pos => pos.Candidates.Any(cand => cand.StatusId == 3))
: false))
.ForMember(x => x.CreatedBy, x => x.MapFrom(c => Mapper.Map<CollaboratorPlainDTO>(c.CreatedBy)))
.ForMember(x => x.UpdatedBy, x => x.MapFrom(c => Mapper.Map<CollaboratorPlainDTO>(c.UpdatedBy)))
.ForMember(x => x.Positions, x => x.MapFrom(c => Mapper.Map<List<PositionDTO>>(c.Positions)))
.PreserveReferences(); // For some reason, this method is not helping actually... the same for MaxDepth(int)
CreateMap<OpportunityDTO, Opportunity>()
.ForMember(x => x.CancellationReason, x => x.Ignore())
.ForMember(x => x.CreatedBy, x => x.Ignore())
.ForMember(x => x.UpdatedBy, x => x.Ignore())
.ForMember(x => x.Positions, x => x.Ignore());
}
private Collaborator GetFromOpportunityRoles(List<OpportunityRole> opportunityRoles, string rol)
{
var opportunityRole = opportunityRoles.FirstOrDefault(opp => opp.ProjectRoleTypeId == rol);
return opportunityRoles != null ? opportunityRole.Collaborator : null;
}
}
Version: x.y.z
6.0.2.0
Expected behavior
A ProjectDTO List;
Actual behavior
a StackOverflow Exception
Steps to reproduce
1. AutoMapperConfig.RegisterMappings();
2- var myOppLogic = new OpportunityLogic();
3. var myEpectedDTO = myOppLogic.GetOpportunitiesWithPositions(1234);
End..
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Comments: 16 (10 by maintainers)
Ok I looked into it a bit more, and you should be using UseAsDataSource instead of ProjectTo for more functionality with OData. You can use things like Filtering and Sort, but not Expand and Select.
This doesn’t solve the issue however. The real problem is in exception description below “The type ‘AutoMapperSample.Controllers.DtoItem’ appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.”
So Automapper is making a query that does new DtoITem in 2 places but in both instances aren’t setting the same properties in the same order. This is probably because of the item and parent item. One item is setting the child, but after max depth is done it won’t set its child and both are structurally incompatible, and thus exception. It’s impossible unless you make an ItemDto, ChildItemDto, GrandChildItemDto, ect Entity Framework just won’t work in this case.
I’m not sure what else to tell you here - first and foremost, I try to remove AutoMapper from the equation to see if I can do what you’re asking manually. If that’s not possible, then AutoMapper certainly can’t do it automatically.
@lbargaoanu I apologize. It did seem to me like they were related at first.
@jbogard No, and this is also new for me I’m afraid. Would it be possible to map expressions on the database entity and not the DTO? If I can map (Amount) => this.SubGroups.Sum(s => s.Quantity) upon the Item entity instead of the DtoItem entity. I need a .Select(s => s.Item.Amount) and navigation properties to work. But without mapping the expression one of course gets the LINQ error: “The specified type member is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.”.
But this has nothing to do with the original question 😃