Skip to content

StackOverflowException when using collections after upgrading to v8.0.0 #188

@Prologh

Description

@Prologh

Intro

Hi,
I've recently upgraded AutoMapper in my project from quite old version 10.1.1 together with AutoMapper.Collection v7.0.1 to the newest AutoMapper v13.0.1 and AutoMapper.Collection v10.0.0. After doing that, my app has been experiencing crashes due to StackOverflowException in AutoMapper mapping code. I've extracted stack trace from crash dumps and figured out which mapping was causing the problem. I've narrowed it down to this Collection library because, same as in this issue commenting out collection mappers fixes the problem and StackOverflow no longer occurs:

//cfg.AddCollectionMappers();

But obviously I still want to use Collections feature. So I've read in another issue that this is related to not preserving references even though they should be preserved by default. At least it works in standalone AutoMapper when not using Collections. But adding PreserveReferences() explicitly on a map does the trick:

cfg.CreateMap<Blog, BlogDto>().PreserveReferences();

Dirty fix

This is still bad when you have hundreds of maps, so I've found a way to apply this globally on all maps as a temporary workaround:

cfg.Internal().ForAllMaps((map, opts) => opts.PreserveReferences());

The title mentions version 8.0.0 because that's the earliest version I've been successful with recreating this problem. From AutoMapper 11.0.1 and AutoMapper.Collection 8.0.0 up until the newest versions, the problem occurs.

Repro

Here's a snippet to recreate StackOverflow:

using AutoMapper;
using AutoMapper.EquivalencyExpression;
using AutoMapper.Internal;

var configuration = new MapperConfiguration(cfg =>
{
    cfg.AddCollectionMappers(); // <-- Culprit.
    cfg.CreateMap<Blog, BlogDto>();
    cfg.CreateMap<Post, PostDto>();
    //cfg.Internal().ForAllMaps((map, opts) => opts.PreserveReferences()); // Global workaround
});
configuration.AssertConfigurationIsValid();
var mapper = configuration.CreateMapper();

var blog = new Blog
{
    Id = 200,
};
var post1 = new Post
{
    Title = "first",
    Blog = blog,
    BlogId = blog.Id,
};
var post2 = new Post
{
    Title = "second",
    Blog = blog,
    BlogId = blog.Id,
};
blog.Posts.Add(post1);
blog.Posts.Add(post2);

var dto = mapper.Map<BlogDto>(blog);

public class BlogDto
{
    public List<PostDto> Posts { get; set; } = [];
}

public class PostDto
{
    public int BlogId { get; set; }

    public BlogDto Blog { get; set; }

    public string Title { get; set; }
}

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

    public List<Post> Posts { get; set; } = [];
}

public class Post
{
    public int BlogId { get; set; }

    public Blog Blog { get; set; }

    public string Title { get; set; }
}

Final note

Yes, I'm aware that keeping cycle references in destination models and mapping it like that as shown above is not a good practice. Yes, I would simply not do it if I had a choice, but we all know that sometimes choice is not something we have. Now, the reason I bring this to your attention is that StackOverflow exceptions are no joke. They can bring down your whole app in seconds and leave you wondering, as no exception will be caught. I had that luck to catch this early on in a testing environment, but to others, fortune might not be as favorable. If this StackOverflow is really rooted in this library, I believe it should be addressed. Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions