AutoMapper and inheritance - How to Map?

前端 未结 5 760
北荒
北荒 2020-12-12 22:24

Have this scenario:

public class Base {  public string Name; }

public Class ClassA :Base {  public int32 Number;  }

public Class ClassB :Base { public st         


        
相关标签:
5条回答
  • 2020-12-12 22:47

    You will need to create DTO classes that match your domain classes like this:

    public class DTO
    {
        public string Name;
    }
    
    public class DTO_A : DTO
    {
        public int Number { get; set; }
    }
    
    public class DTO_B : DTO
    {
        public string Description { get; set; }
    }
    

    You then need to change your mappings to this:

            Mapper.CreateMap<Base, DTO>()
                .Include<ClassA, DTO_A>()
                .Include<ClassB, DTO_B>();
    
            Mapper.CreateMap<ClassA, DTO_A>();
    
            Mapper.CreateMap<ClassB, DTO_B>();
    
            Mapper.AssertConfigurationIsValid();
    

    Once this is done, then the following will work:

            var baseList = new List<Base>
            {
                new Base {Name = "Base"},
                new ClassA {Name = "ClassA", Number = 1},
                new ClassB {Name = "ClassB", Description = "Desc"},
            };
    
            var test = Mapper.Map<IList<Base>,IList<DTO>>(baseList);
            Console.WriteLine(test[0].Name);
            Console.WriteLine(test[1].Name);
            Console.WriteLine(((DTO_A)test[1]).Number);
            Console.WriteLine(test[2].Name);
            Console.WriteLine(((DTO_B)test[2]).Description);
            Console.ReadLine();
    

    Unfortunately this does mean that you have an unwanted cast, but I don't think there's much that you can do about that.

    0 讨论(0)
  • 2020-12-12 22:49

    I did this to solve the problem

    IList<DTO> list1 = AutoMapper.Mapper.Map<IList<ClassA>,IList<DTO>>(baseList.OfType<ClassA>().ToList());
    
    IList<DTO> list2 = AutoMapper.Mapper.Map<IList<ClassB>,IList<DTO>>(baseList.OfType<ClassB>().ToList());
    
    list = list1.Union(list2);
    
    persons.OfType<T>().ToList()
    

    Must be a better way to do this.

    0 讨论(0)
  • 2020-12-12 22:52

    Following on from Eugene Gorbovoy's answer, if you're using profiles to configure your AutoMapper, you need to use a TypeConverter.

    Create a new TypeConverter like this

        public class NumberConverter : ITypeConverter<DTO, NumberBase>
        {
            public NumberBase Convert(DTO source, NumberBase destination, ResolutionContext context)
            {
                if (source.Id % 2 == 0)
                {
                    return context.Mapper.Map<EvenNumber>(source);
                }
                else
                {
                    return context.Mapper.Map<OddNumber>(source);
                }
            }
        }
    

    and replace the ConvertUsing line in his example with

      expression.CreateMap<DTO, NumberBase>()
                .ConvertUsing(new NumberConverter());
    
    0 讨论(0)
  • 2020-12-12 22:57

    For your scenario you have to use IMappingExpression.ConvertUsing method. By using it you can provide appropriate type for newly created object. Please, look at my example (pretty well fit your scenario):

    using System;
    using System.Linq;
    using AutoMapper;
    
    namespace ConsoleApplication19
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                //mapping
                Mapper.Initialize(expression =>
                {
                    expression.CreateMap<DTO, NumberBase>()
                        .ForMember(@class => @class.IdOnlyInDestination,
                            configurationExpression => configurationExpression.MapFrom(dto => dto.Id))
                        .ConvertUsing(dto =>//here is the function that creates appropriate object
                        {
                            if (dto.Id%2 == 0) return Mapper.Map<EvenNumber>(dto);
                            return Mapper.Map<OddNumber>(dto);
                        });
    
                    expression.CreateMap<DTO, OddNumber>()
                        .IncludeBase<DTO, NumberBase>();
    
                    expression.CreateMap<DTO, EvenNumber>()
                        .IncludeBase<DTO, NumberBase>();
                });
    
                //initial data
                var arrayDto = Enumerable.Range(0, 10).Select(i => new DTO {Id = i}).ToArray();
    
                //converting
                var arrayResult = Mapper.Map<NumberBase[]>(arrayDto);
    
                //output
                foreach (var resultElement in arrayResult)
                {
                    Console.WriteLine($"{resultElement.IdOnlyInDestination} - {resultElement.GetType().Name}");
                }
    
                Console.ReadLine();
            }
        }
    
        public class DTO
        {
            public int Id { get; set; }
    
            public int EvenFactor => Id%2;
        }
    
        public abstract class NumberBase
        {
            public int Id { get; set; }
            public int IdOnlyInDestination { get; set; }
        }
    
        public class OddNumber : NumberBase
        {
            public int EvenFactor { get; set; }
        }
    
        public class EvenNumber : NumberBase
        {
            public string EventFactor { get; set; }
        }
    }
    
    0 讨论(0)
  • 2020-12-12 23:03

    At least with recent Automapper versions (>2.0?) your code is okay if you remove the IList<>:s of your first CreateMap statement1. And you don't have to create specific DTO classes as @Simon suggests in another answer (unless that's what you want).

    But to be specific about the inheritance and to avoid redundant mapping clauses when you extend the base class you can specify the inheritance by using the .Include method. So, if you create your mappings like this:

    Mapper.CreateMap<Base, DTO>()
        .Include<ClassA, DTO>()
        .Include<ClassB, DTO>()
        .ForMember(dest => dest.Description, opt => opt.Ignore())
        .ForMember(dest => dest.Number, opt => opt.Ignore());
    
    Mapper.CreateMap<ClassA, DTO>()
        .ForMember(dest => dest.Description, opt => opt.Ignore());
    
    Mapper.CreateMap<ClassB, DTO>()
        .ForMember(dest => dest.Number, opt => opt.Ignore());
    
    Mapper.AssertConfigurationIsValid(); //Is OK!
    

    then you can do this:

    var baseList = new List<Base>
    {
        new Base {Name = "Base"},
        new ClassA {Name = "ClassA", Number = 1},
        new ClassB {Name = "ClassB", Description = "Desc"},
    };
    
    var test = Mapper.Map<IList<Base>, IList<DTO>>(baseList);
    Console.WriteLine(test[0].Name);
    Console.WriteLine(test[1].Name);
    Console.WriteLine((test[1]).Number);
    Console.WriteLine(test[2].Name);
    Console.WriteLine((test[2]).Description);
    Console.ReadLine();
    

    (Note that you don't have to map IList specifically. Automapper handles this for you.)
    See this article about .Include.

    1Actually I wonder if the code compiled as written in the question?

    0 讨论(0)
提交回复
热议问题