Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper and interface typed collections

I'm new to AutoMapper. Sorry if this is too simple.

This is my sample domain:

I have a Basket. It contains a list of Food. Food is either Banana or Pickle.

I have DTOs that mirror each class in the domain. The goal: from a BasketDto, map it and its contents to a Basket.

This is code that fails. After the last line I have a Basket, but it's filled with DTOs instead of regular entities :(

class Program
{
    static void Main(string[] args)
    {
        Mapper.CreateMap<BasketDto, Basket>();
        Mapper.CreateMap<PickleDto, Pickle>();
        Mapper.CreateMap<BananaDto, Banana>();

        var dto = new BasketDto
                  {
                      Food = new List<IFood>
                             {
                                 new PickleDto { Name = "BigPickle" },
                                 new BananaDto { Name = "SmallBanana" },
                             }
                  };

        var basketFromDto = Mapper.Map<Basket>(dto);
    }
}

// Domain classes and interfaces --------------

interface IFood
{
    string Name { get; set; }
}

class Banana : IFood
{
    public string Name { get; set; }
}

class Pickle : IFood
{
    public string Name { get; set; }
}

class Basket
{
    public IList<IFood> Food { get; set; }
}

// DTOs -------------

class BasketDto
{
    public IList<IFood> Food { get; set; }
}

class PickleDto : IFood
{
    public string Name { get; set; }
}

class BananaDto : IFood
{
    public string Name { get; set; }
}

What should I do to Map also the Children using Food as a IList? Mappin interfaces and hierarchies is really complex!

Thanks a lot.

like image 257
SuperJMN Avatar asked Dec 15 '22 10:12

SuperJMN


2 Answers

While @Mightymuke's answer is correct based on the original question, there is something else to be considered. From OOP stand point, interfaces describe behavior, and IFood in the example is not a behavior. Using a base Food class, and the build-in inheritance mapping in Automapper, it's more natural:

namespace Stackoverflow
{
    public class Food
    {
        public virtual string Name { get; set; }
    }

    public class Banana : Food
    {
    }

    public class Pickle : Food
    {
    }

    public class Basket
    {
        public IList<Food> Food { get; set; }
    }

    public class FoodDto
    {
        public virtual string Name { get; set; }
    }

    public class BananaDto : FoodDto
    {
    }

    public class PickleDto : FoodDto
    {
    }

    public class BasketDto
    {
        public IList<FoodDto> Food { get; set; }
    }

    [TestFixture]
    public class InheritanceMappingTests
    {
        [Test]
        public void Should_map_inherited_classes()
        {
            //arrange
            var basketDto = new BasketDto
                {
                    Food = new List<FoodDto>
                        {
                            new BananaDto {Name = "banana"},
                            new PickleDto {Name = "pickle"}
                        }
                };

            Mapper.CreateMap<FoodDto, Food>()
                  .Include<BananaDto, Banana>()
                  .Include<PickleDto, Pickle>();
            Mapper.CreateMap<BananaDto, Banana>();
            Mapper.CreateMap<PickleDto, Pickle>();
            Mapper.CreateMap<BasketDto, Basket>();

            Mapper.AssertConfigurationIsValid();

            //act
            var basket = Mapper.Map<Basket>(basketDto);

            //assert
            Assert.That(basket.Food[0].GetType() == typeof(Banana));
            Assert.That(basket.Food[0].Name == "banana");
            Assert.That(basket.Food[1].GetType() == typeof(Pickle));
            Assert.That(basket.Food[1].Name == "pickle");
        }
    }
}
like image 118
Sunny Milenov Avatar answered Jan 06 '23 19:01

Sunny Milenov


The problem here is that AutoMapper doesn't know how you want it converted. All the Food items derive from IFood, so the mapping its performing is the simplest (and is correct). You can force the appropriate mapping by creating a TypeConverter - something like this might work:

public class FoodConverter : TypeConverter<IFood, IFood>
{
    protected override IFood ConvertCore(IFood source)
    {
        if (source is PickleDto) return Mapper.Map<Pickle>(source);
        if (source is BananaDto) return Mapper.Map<Banana>(source);
        return null;
    }
}

This can be configured in your mapping like this:

Mapper.CreateMap<IFood, IFood>().ConvertUsing<FoodConverter>();

Personally I would take it a little further have have the DTO food items derive from:

interface IFoodDto
{
    string Name { get; set; }
}

This will make your intention a little clearer to AutoMapper.

Finally, dont forget to call AssertConfigurationIsValid in your mapping.

like image 31
Mightymuke Avatar answered Jan 06 '23 19:01

Mightymuke