C# AutoMapper issue with mapping Many-to-Many, wrong foreign key

Viewed 241

I have faced with the strange issue with mapping model to entity with Many-to-Many relationship

I have the following entities and models:

using System;
using System.Diagnostics;
using System.Collections.Generic;

namespace AutoMapper.ReproducedExample
{
    public class StoreEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<StoreProductEntity> Products { get; set; } = new List<StoreProductEntity>();
    }

    public class ProductEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<StoreProductEntity> Stores { get; set; } = new List<StoreProductEntity>();
    }

    public class StoreProductEntity
    {
        public int StoreId { get; set; }
        public StoreEntity Store { get; set; }
        public int ProductId { get; set; }
        public ProductEntity Product { get; set; }
    }

    public class StoreModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<ProductModel> Products { get; set; } = new List<ProductModel>();
    }

    public class ProductModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<StoreModel> Stores { get; set; } = new List<StoreModel>();
    }

    public class CustomProfile : Profile
    {
        public CustomProfile()
        {
            CreateMap<StoreModel, StoreEntity>()
                .ForMember(d => d.Products,
                    opt => opt.MapFrom(s => s.Products))
                .AfterMap((model, entity) =>
                {
                    foreach (var entityProduct in entity.Products)
                    {
                        entityProduct.StoreId = entity.Id;
                        entityProduct.Store = entity;
                    }
                });
            CreateMap<StoreModel, StoreProductEntity>()
                .ForMember(entity => entity.StoreId, opt => opt.MapFrom(model => model.Id))
                .ForMember(entity => entity.Store, opt => opt.MapFrom(model => model))
                .ForMember(entity => entity.ProductId, opt => opt.Ignore())
                .ForMember(entity => entity.Product, opt => opt.Ignore());

            CreateMap<ProductModel, ProductEntity>()
                .ForMember(d => d.Stores,
                    opt => opt.MapFrom(s => s.Stores))
                .AfterMap((model, entity) =>
                {
                    foreach (var entityStore in entity.Stores)
                    {
                        entityStore.ProductId = entity.Id;
                        entityStore.Product = entity;
                    }
                });
            CreateMap<ProductModel, StoreProductEntity>()
                .ForMember(entity => entity.StoreId, opt => opt.Ignore())
                .ForMember(entity => entity.Store, opt => opt.Ignore())
                .ForMember(entity => entity.ProductId, opt => opt.MapFrom(model => model.Id))
                .ForMember(entity => entity.Product, opt => opt.MapFrom(model => model));

        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var configuration = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile<CustomProfile>();
            });
#if DEBUG
            configuration.AssertConfigurationIsValid();
#endif
            var mapper = configuration.CreateMapper();
            var store0 = new StoreModel()
            {
                Id = 1,
                Name = "Store0",
            };
            var store1 = new StoreModel()
            {
                Id = 2,
                Name = "Store1",
            };
            var product = new ProductModel()
            {
                Id = 1,
                Name = "Product",
            };
            store1.Products.Add(product);
            product.Stores.Add(store1);
            store0.Products.Add(product);
            product.Stores.Add(store0);
            
            var store0Entity = mapper.Map<StoreEntity>(store0);
            Debug.Assert(store0Entity.Products[0].Product.Stores[0].Store.Id ==
                         store0Entity.Products[0].Product.Stores[0].Store.Products[0].StoreId);
        }
    }
}

Mapping pass successfully, but by some reason some deep key is not mapped to the related StoreEntity

The following assertion is failed ...

Debug.Assert(store0Entity.Products[0].Product.Stores[0].Store.Id ==
             store0Entity.Products[0].Product.Stores[0].Store.Products[0].StoreId);

Seems like by some reason in depth Store class AutoMapper uses wrong mapped List ... but I am not sure ...

1 Answers

I have figured out how to fixed this issue (thanks to answer on question Automapper creates 2 instances of one object)

For detecting Circular references I can add .PreserveReferences() method for mapping

Here is proper CustomProfile:

    public class CustomProfile : Profile
    {
        public CustomProfile()
        {
            CreateMap<StoreModel, StoreEntity>()
                .ForMember(d => d.Products,
                    opt => opt.MapFrom(s => s.Products))
                .AfterMap((model, entity) =>
                {
                    foreach (var entityProduct in entity.Products)
                    {
                        entityProduct.StoreId = entity.Id;
                        entityProduct.Store = entity;
                    }
                })
                .PreserveReferences();
            CreateMap<StoreModel, StoreProductEntity>()
                .ForMember(entity => entity.StoreId, opt => opt.MapFrom(model => model.Id))
                .ForMember(entity => entity.Store, opt => opt.MapFrom(model => model))
                .ForMember(entity => entity.ProductId, opt => opt.Ignore())
                .ForMember(entity => entity.Product, opt => opt.Ignore());

            CreateMap<ProductModel, ProductEntity>()
                .ForMember(d => d.Stores,
                    opt => opt.MapFrom(s => s.Stores))
                .AfterMap((model, entity) =>
                {
                    foreach (var entityStore in entity.Stores)
                    {
                        entityStore.ProductId = entity.Id;
                        entityStore.Product = entity;
                    }
                })
                .PreserveReferences();
            CreateMap<ProductModel, StoreProductEntity>()
                .ForMember(entity => entity.StoreId, opt => opt.Ignore())
                .ForMember(entity => entity.Store, opt => opt.Ignore())
                .ForMember(entity => entity.ProductId, opt => opt.MapFrom(model => model.Id))
                .ForMember(entity => entity.Product, opt => opt.MapFrom(model => model));
        }
    }

But actually I did not know if it is an issue or not, because according the documentation AutoMapper should detect Circular References automatically for AutoMapper version 10.0.0 and seems like this is an issue

Related