web-dev-qa-db-ja.com

AutoMapperと継承-マップする方法

このシナリオがあります:

public class Base {  public string Name; }

public Class ClassA :Base {  public int32 Number;  }

public Class ClassB :Base { public string Description;}

public Class DTO {
  public string Name;
  public int32 Number;
  public string Description;
}

を持っています IList<Base>私の地図は:

AutoMapper.Mapper.CreateMap<IList<Base>, IList<DTO>>()
   .ForMember(dest => dest.Number, opt => opt.Ignore())
   .ForMember(dest => dest.Description, opt => opt.Ignore());

AutoMapper.Mapper.CreateMap<ClassA, DTo>()
   .ForMember(dest => dest.Description, opt => opt.Ignore());

AutoMapper.Mapper.CreateMap<ClassB, DTO>()
   .ForMember(dest => dest.Number, opt => opt.Ignore())

Mapper.AssertConfigurationIsValid(); //Is OK!

しかし、ClassAまたはClassBにあるプロパティは、これを実行するとマップされません。

IList<DTO>= AutoMapper.Mapper.Map<IList<Base>,IList<DTO>>(baseList);

ClasAClassBで定義されているプロパティをマップするにはどうすればよいですか

38
Gringo

次のように、ドメインクラスに一致するDTOクラスを作成する必要があります。

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; }
}

次に、マッピングを次のように変更する必要があります。

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

        Mapper.CreateMap<ClassA, DTO_A>();

        Mapper.CreateMap<ClassB, DTO_B>();

        Mapper.AssertConfigurationIsValid();

これが完了すると、以下が機能します。

        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();

残念ながら、これは不要なキャストがあることを意味しますが、それについてできることは多くないと思います。

76
Simon

少なくとも最近のAutomapperバージョン(> 2.0?)では、最初のCreateMapステートメントのIList<>:sを削除すれば、コードは問題ありません。1。また、@ Simonが別の回答で提案しているように、特定のDTOクラスを作成する必要はありません(それが必要な場合を除きます)。

ただし、継承について具体的に説明し、基本クラスを拡張するときに重複するマッピング句を回避するには、.Includeメソッドを使用して継承を指定できます。したがって、次のようにマッピングを作成すると、

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!

その後、これを行うことができます:

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();

(IListを具体的にマップする必要がないことに注意してください。オートマッパーがこれを処理します。)
この記事を参照.Includeについて。

1実際にコードは質問に書かれたとおりにコンパイルされているのだろうか?

4
Ulf Åkerstedt

Eugene Gorbovoyの回答に続き、AutoMapperの構成にプロファイルを使用している場合は、TypeConverterを使用する必要があります。

このように新しいTypeConverterを作成します

    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);
            }
        }
    }

そして、彼の例のConvertUsing行を

  expression.CreateMap<DTO, NumberBase>()
            .ConvertUsing(new NumberConverter());
2
podiluska

問題を解決するためにこれを行いました

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()

これを行うにはより良い方法でなければなりません。

0
Gringo

シナリオでは、IMappingExpression.ConvertUsingメソッドを使用する必要があります。これを使用することで、新しく作成されたオブジェクトに適切なタイプを提供できます。私の例を見てください(あなたのシナリオにかなりよく合います):

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
Eugene Gorbovoy