Лучший способ использовать AutoMapper для выравнивания вложенных объектов? - PullRequest
22 голосов
/ 05 июля 2011

Я сплющил доменные объекты в DTO, как показано в примере ниже:

public class Root
{
    public string AParentProperty { get; set; }
    public Nested TheNestedClass { get; set; }
}

public class Nested
{
    public string ANestedProperty { get; set; }
}

public class Flattened
{
    public string AParentProperty { get; set; }
    public string ANestedProperty { get; set; }
}

// I put the equivalent of the following in a profile, configured at application start
// as suggested by others:

Mapper.CreateMap<Root, Flattened>()
      .ForMember
       (
          dest => dest.ANestedProperty
          , opt => opt.MapFrom(src => src.TheNestedClass.ANestedProperty)
       );

// This is in my controller:
Flattened myFlattened = Mapper.Map<Root, Flattened>(myRoot);

Я рассмотрел несколько примеров, и до сих пор это похоже на способ сглаживания вложенной иерархии.,Однако, если дочерний объект имеет ряд свойств, такой подход не сильно экономит кодирование.

Я нашел этот пример:

http://consultingblogs.emc.com/owainwragg/archive/2010/12/22/automapper-mapping-from-multiple-objects.aspx

, но для этого требуются экземпляры сопоставленных объектов, требуемые функцией Map (), которая не будет работать спрофиль, как я понимаю.

Я новичок в AutoMapper, поэтому я хотел бы знать, есть ли лучший способ сделать это.

Ответы [ 6 ]

14 голосов
/ 24 ноября 2011

В последней версии AutoMapper есть соглашение об именах, которое вы можете использовать, чтобы избежать нескольких операторов .ForMember.

В вашем примере, если вы обновите свой класс Flatlined следующим образом:

public class Flattened
{
    public string AParentProperty { get; set; }
    public string TheNestedClassANestedProperty { get; set; }
}

Вы можете избежать использования оператора ForMember:

Mapper.CreateMap<Root, Flattened>();

Automapper (в соответствии с соглашением) отобразит Root.TheNestedClass.ANestedProperty в Flattened.TheNestedClassANestedProperty в этом случае.Честно, выглядит менее уродливо, когда вы используете настоящие имена классов!

5 голосов
/ 01 октября 2014

2 возможных решения:

Mapper.CreateMap<Nested, Flattened>()
    .ForMember(s=>s.AParentProperty, o=>o.Ignore());
Mapper.CreateMap<Root, Flattened>()
    .ForMember(d => d.ANestedProperty, o => o.MapFrom(s => s.TheNestedClass));

Альтернативный подход был бы ниже, но он не прошел бы Mapper.AssertConfigurationIsValid().

Mapper.CreateMap<Nested, Flattened>()
//.ForMember map your properties here
Mapper.CreateMap<Root, Flattened>()
//.ForMember... map you properties here
.AfterMap((s, d) => Mapper.Map(s.TheNestedClass, d));
3 голосов
/ 22 мая 2018

Не уверен, добавляет ли это ценность предыдущим решениям, но вы можете сделать это как двухэтапное отображение. Будьте внимательны при отображении в правильном порядке, если есть конфликт имен между родителем и потомком (последние победы).

        Mapper.CreateMap<Root, Flattened>();
        Mapper.CreateMap<Nested, Flattened>();

        var flattened = new Flattened();
        Mapper.Map(root, flattened);
        Mapper.Map(root.TheNestedClass, flattened);
2 голосов
/ 21 февраля 2019

Я предпочитаю избегать старых статических методов и делаю это так.

Поместите наши определения сопоставления в Профиль . Сначала мы сопоставляем Корень, а затем применяем сопоставления Вложенных. Обратите внимание на использование Context .

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Root, Flattened>()
            .AfterMap((src, dest, context) => context.Mapper.Map(src.TheNestedClass, dest));
        CreateMap<Nested, Flattened>();
    }
}

Преимущество определения сопоставления от Корень до Сглаженный и Вложенный до Плоский * заключается в том, что вы сохраняете полный контроль над сопоставление свойств, например, если имя свойства назначения отличается или вы хотите применить преобразование и т. д.

Тест XUnit:

[Fact]
public void Mapping_root_to_flattened_should_include_nested_properties()
{
    // ARRANGE
    var myRoot = new Root
    {
        AParentProperty = "my AParentProperty",
        TheNestedClass = new Nested
        {
            ANestedProperty = "my ANestedProperty"
        }
    };

    // Manually create the mapper using the Profile
    var mapper = new MapperConfiguration(cfg => cfg.AddProfile(new MappingProfile())).CreateMapper();

    // ACT
    var myFlattened = mapper.Map<Root, Flattened>(myRoot);

    // ASSERT
    Assert.Equal(myRoot.AParentProperty, myFlattened.AParentProperty);
    Assert.Equal(myRoot.TheNestedClass.ANestedProperty, myFlattened.ANestedProperty);
}

При добавлении serviceCollection.AddAutoMapper () AutoMapper из пакета AutoMapper.Extensions.Microsoft.DependencyInjection к вашему запуску профиль будет выбран автоматически, и вы сможете просто вставьте IMapper туда, где вы применяете сопоставление.

1 голос
/ 07 марта 2018

Чтобы улучшить другой ответ, задайте MemberList.Source для обоих сопоставлений и установите, чтобы вложенное свойство игнорировалось. Проверка затем проходит ОК.

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<SrcNested, DestFlat>(MemberList.Source);
    cfg.CreateMap<SrcRoot, DestFlat>(MemberList.Source)
        .ForSourceMember(s => s.Nested, x => x.Ignore())
        .AfterMap((s, d) => Mapper.Map(s.Nested, d));
});

Mapper.AssertConfigurationIsValid();

var dest = Mapper.Map<SrcRoot, DestFlat>(src);
1 голос
/ 23 мая 2016

Я написал метод расширения для решения аналогичной проблемы:

public static IMappingExpression<TSource, TDestination> FlattenNested<TSource, TNestedSource, TDestination>(
    this IMappingExpression<TSource, TDestination> expression,
    Expression<Func<TSource, TNestedSource>> nestedSelector,
    IMappingExpression<TNestedSource, TDestination> nestedMappingExpression)
{
    var dstProperties = typeof(TDestination).GetProperties().Select(p => p.Name);

    var flattenedMappings = nestedMappingExpression.TypeMap.GetPropertyMaps()
                                                    .Where(pm => pm.IsMapped() && !pm.IsIgnored())
                                                    .ToDictionary(pm => pm.DestinationProperty.Name,
                                                                    pm => Expression.Lambda(
                                                                        Expression.MakeMemberAccess(nestedSelector.Body, pm.SourceMember),
                                                                        nestedSelector.Parameters[0]));

    foreach (var property in dstProperties)
    {
        if (!flattenedMappings.ContainsKey(property))
            continue;

        expression.ForMember(property, opt => opt.MapFrom((dynamic)flattenedMappings[property]));
    }

    return expression;
}

Так что в вашем случае это можно использовать так:

var nestedMap = Mapper.CreateMap<Nested, Flattened>()
                      .IgnoreAllNonExisting();

Mapper.CreateMap<Root, Flattened>()
      .FlattenNested(s => s.TheNestedClass, nestedMap);

IgnoreAllNonExisting() от здесь .

Хотя это не универсальное решение, этого должно быть достаточно для простых случаев.

Итак,

  1. Вам не нужно следовать соглашению о выравнивании в свойствах пункта назначения
  2. Mapper.AssertConfigurationIsValid () пройдет
  3. Вы также можете использовать этот метод в нестатическом API (профиль)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...