Search code examples
c#entity-frameworkautomapperautomapper-6

Self referencing loop causing stack overflow with map


According to the documentation PreserveReferences should be set automatically whenever possible for AutoMapper.

Starting from 6.1.0 PreserveReferences is set automatically at config time whenever possible.

https://github.com/AutoMapper/AutoMapper/wiki/5.0-Upgrade-Guide

I have also tried setting MaxDepth to 1 but I still get a stack overflow exception with the following mapping. Can I get around this somehow or do I need to modify the view models?

cfg.CreateMap<ArticleViewModel, Article>(MemberList.Source)
    .MaxDepth(1)
    .EqualityComparison((src, dst) => src.Id == dst.Id);

Code that causes the stack overflow exception:

var article = await iArticleRepository.GetAsync(id);
//The mapping below causes the exception
var mappedArticle = Mapper.Map<ArticleViewModel>(article);

Entities:

public class Article: IEntity<int>
{
    [Key]
    public int Id { get; set; }

    ...

    public int SupplierId { get; set; }

    public virtual Supplier Supplier { get; set; }
}

public class Supplier: IEntity<int>
{
    [Key]
    public int Id { get; set; }

    ...

    public virtual ICollection<Contact> Contacts { get; set; }
}

public class Contact: IEntity<int>
{
    [Key]
    public int Id { get; set; }

    ...
    public virtual ICollection<Supplier> Suppliers { get; set; }
}

View models:

public class ArticleViewModel
{
    public int Id { get; set; }

    ...
    public SupplierViewModel Supplier { get; set; }

}

public class SupplierViewModel
{
    public int Id { get; set; }

    ...
    public List<ContactViewModel> Contacts { get; set; }

}

public class ContactViewModel
{
    public int Id { get; set; }
    ... 
    public List<SupplierViewModel> Suppliers { get; set; }
}

Solution

  • Well, it's unclear what does whenever possible mean. Since the documentation before that states

    It turns out this tracking is very expensive, and you need to opt-in using PreserveReferences for circular maps to work

    looks like your scenario fails into not possible category :)

    Let not rely on that and use the explicit opt-in. The circular reference in this sample model is between Supplier and Contact, so you have to specify in one of the involved class mappings, for instance:

    cfg.CreateMap<ArticleViewModel, Article>(MemberList.Source)
        .MaxDepth(1)
        .EqualityComparison((src, dst) => src.Id == dst.Id);
    
    cfg.CreateMap<SupplierViewModel, Supplier>(MemberList.Source)
        .PreserveReferences()
        .EqualityComparison((src, dst) => src.Id == dst.Id);