Automapper with Record types causing StackOverflowException

71 Views Asked by At

Mapping from Entity type to DTO type using AutoMapper causes StackOverflow exception due to referential loops when I'm using records for DTOs. However, when I use classes for DTOs I still have an infinite nesting, but it's not throwing a StackOverflowException and this can be fixed by configuring JSON to ignore Referential Loops.

I tried to configure mapping with MaxDepth, PreserveReferences(), Ignoring properties like this:

CreateMap<Category, CategoryDto>()
    .ForMember(x => x.Products, r => r.Ignore());

none of these approaches worked, I found similar questions on SO and issues on GitHub, but still can't figure out the reason of why it's happening and how to fix it properly.

Entities

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public int CategoryId { get; set; }
    public virtual Category Category { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string? Description { get; set; }

    public virtual ICollection<Product> Products { get; set; }
}

DTOs

public record ProductDto(
    int Id,
    string Name,
    decimal Price
    CategoryDto Category);

public record CategoryDto(
    int Id,
    string Name,
    string? Description
    ICollection<ProductDto> Products);

Mappings

public class ProductMappings : Profile
{
    public ProductMappings()
    {
        CreateMap<Product, ProductDto>()
            .PreserveReferences();
    }
}

public class CategoryMappings : Profile
{
    public CategoryMappings()
    {
        CreateMap<Category, CategoryDto>()
            .PreserveReferences();
    }
}

EF Core query logic

public async Task<PaginatedList<Category>> FindAllAsync(CategoryQueryParameters queryParameters)
{
    var query = _context.Categories.AsQueryable();

    if (!string.IsNullOrWhiteSpace(queryParameters.SearchQuery))
    {
        query = query.Where(x => x.Name.Contains(queryParameters.SearchQuery, StringComparison.OrdinalIgnoreCase));
    }

    var entities = await query
        .Include(x => x.Products)
        .AsNoTrackingWithIdentityResolution()
        .ToPaginatedListAsync(queryParameters.PageNumber, queryParameters.PageSize);

    return entities;
}

Service logic where mapping happens

public async Task<IEnumerable<CategoryDto>> GetAsync(CategoryQueryParameters queryParameters)
{
    var paginatedResult = await _repository.ProductCategory.FindAllAsync(queryParameters);

    return _mapper.Map<IEnumerable<CategoryDto>>(paginatedResult);
}

Here is the log from database call: enter image description here

I understand that it can be fixed by changing the design and removing nested references in DTOs, but I'm curios about the reason of why it's happening. Initially I thought that AutoMapper is unable to handle in record types loop references due to record types being compared by value, and not reference, but this does not appear to be the case.

0

There are 0 best solutions below