Используйте AutoMapper для преобразования реляционных моделей dto в модели полиморфных доменов - PullRequest
0 голосов
/ 22 января 2019

В моей базе данных у меня есть базовая таблица с 2 таблицами подробностей, каждая запись в базовой таблице будет иметь только одну запись подробностей.Я пытаюсь сопоставить эти 3 таблицы двум моделям домена, которые наследуются от модели общего базового домена.Обычно это называется наследованием таблиц на типы в EntityFramework, но в EntityFrameworkCore оно не реализовано.

Мои модели dto выглядят как

public class SourceBase
{
   public int Id {get;set;}
   public string Value {get;set;}
   public SourceA A {get;set;}
   public SourceB B {get;set;}
}

public class SourceA
{
    public string ValueA {get;set;}
}

public class SourceB
{
    public string ValueB {get;set;}
}

Мои модели желаемых доменов выглядят как

public class TargetBase
{
    public int Id {get;set;}
    public string Value {get;set;}
}

public class TargetA : TargetBase 
{
    public string ValueA {get;set;}
}

public class TargetB : TargetBase
{
    public string ValueB {get;set;}
}

Как мне настроить сопоставления для этого?


Моя самая успешная попытка была:

CreateMap<SourceBase, TargetBase>().ConvertUsing<CustomTypeConverter>();
CreateMap<SourceA, TargetA>();
CreateMap<SourceB, TargetB>();

с CustomTypeConverter:

public class CustomTypeConverter : ITypeConverter<SourceBase, TargetBase>
{
    public TargetBase Convert(SourceBase source, TargetBase destination, ResolutionContext context)
   {
       if (source.A == null)
       {
           return context.Mapper.Map<SourceB, TargetB>(source.B);
       }
       else if (source.B == null)
       {
           return context.Mapper.Map<SourceA, TargetA>(source.A);
       }
       return null;
    }
}

, которая правильно дает мне тип TargetA или TargetB, но ни одно из значений TargetBaseсопоставлены.

Ответы [ 3 ]

0 голосов
/ 01 февраля 2019

Я уверен, что есть и другие способы, но он может работать почти так же, как вы пробовали.

Mapper.Initialize(cfg =>
{
    cfg.CreateMissingTypeMaps = false;
    cfg.CreateMap<SourceBase, TargetBase>().ConvertUsing(s=>Mapper.Map(s, s.A == null ? (TargetBase)new TargetB() : new TargetA()));
    cfg.CreateMap<SourceBase, TargetA>().ForMember(d=>d.ValueA, o=>o.MapFrom(s=>s.A.ValueA));
    cfg.CreateMap<SourceBase, TargetB>().ForMember(d=>d.ValueB, o=>o.MapFrom(s=>s.B.ValueB));
});

MapFroms не требуются, если вы следуете соглашению об именах илииспользуйте метод расширения .

0 голосов
/ 01 февраля 2019

Очень похожий ответ на вопрос Люциана.Я предпочитаю это, поскольку он явно указывает на то, что он выбирает конкретное производное отображение классов, но фактически это тот же ответ.

        Mapper.Initialize(cfg =>
        {
            cfg.CreateMap<SourceBase, TargetBase>().ConvertUsing(s => s.A == null ? (TargetBase)Mapper.Map<SourceBase, TargetB>(s) : Mapper.Map<SourceBase, TargetA>(s));
            cfg.CreateMap<SourceBase, TargetA>().ForMember(d => d.ValueA, o => o.MapFrom(s => s.A.ValueA));
            cfg.CreateMap<SourceBase, TargetB>().ForMember(d => d.ValueB, o => o.MapFrom(s => s.B.ValueB));
        });
0 голосов
/ 01 февраля 2019

Я нашел самый простой способ для этого - использовать ConstructUsing и AfterMap, чтобы избежать рекурсии:

public class Program
{
    public static void Main()
    {
        AutoMapper.Mapper.Reset();
        AutoMapper.Mapper.Initialize(cfg =>
        {
            cfg.CreateMap<SourceBase, TargetBase>().ConstructUsing(x => Convert(x)).AfterMap(Transform);
            cfg.CreateMap<SourceA, TargetA>();
            cfg.CreateMap<SourceB, TargetB>();
        });

        var src = new SourceBase
        {
            Id = 3,
            Value = "Asd",
            A = new SourceA
            {
                ValueA = "Qwe"
            }
        };
        var a = AutoMapper.Mapper.Map<SourceBase, TargetBase>(src);
        a.Dump(); // Code for LinqPad to show the result
    }

    public static TargetBase Convert(SourceBase source)
    {
        if (source.A == null)
        {
            return new TargetB();
        }
        else if (source.B == null)
        {
            return new TargetA();
        }

        return null;
    }

    public static void Transform(SourceBase source, TargetBase target)
    {
        if (source.A == null)
        {
            AutoMapper.Mapper.Map<SourceB, TargetB>(source.B, (TargetB)target);
        }
        else if (source.B == null)
        {
            AutoMapper.Mapper.Map<SourceA, TargetA>(source.A, (TargetA)target);
        }
    }
}

public class SourceBase
{
   public int Id {get;set;}
   public string Value {get;set;}
   public SourceA A {get;set;}
   public SourceB B {get;set;}
}

public class SourceA
{
    public string ValueA {get;set;}
}

public class SourceB
{
    public string ValueB {get;set;}
}

public class TargetBase
{
    public int Id {get;set;}
    public string Value {get;set;}
}

public class TargetA : TargetBase 
{
    public string ValueA {get;set;}
}

public class TargetB : TargetBase
{
    public string ValueB {get;set;}
}

Очевидно, что вы можете поместить методы преобразования в собственный класс

...