web-dev-qa-db-ja.com

AutoMapperを使用してネストされたオブジェクトをフラット化するためのより良い方法はありますか?

以下の例に示すように、ドメインオブジェクトを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を初めて使用するので、これを行うためのより良い方法があるかどうかを知りたいと思います。

24
John

私は古い静的メソッドを避けて、このようにすることを強く望んでいます。

マッピング定義をプロファイルに配置します。最初にルートをマッピングし、その後、ネストされたマッピングを適用します。 Contextの使用に注意してください。

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

RootからFlattenedおよびNestedからの両方のマッピングを定義する利点Flatternedは、宛先プロパティ名が異なる場合や変換を適用する場合など、プロパティのマッピングを完全に制御できることです。

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

AutoMapperのserviceCollection.AddAutoMapper()AutoMapper.Extensions.Microsoft.DependencyInjection nugetパッケージからスタートアップに追加すると、プロファイルが自動的に取得されます。 、およびマッピングを適用する場所にIMapperを挿入するだけです。

7
Lee Oades

AutoMapperの最新バージョンには、複数の.ForMemberステートメントを回避するために使用できる命名規則があります。

あなたの例では、Flattenedクラスを次のように更新した場合:

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

ForMemberステートメントの使用を回避できます。

Mapper.CreateMap<Root, Flattened>();

Automapperは(慣例により)マップしますRoot.TheNestedClass.ANestedPropertyからFlattened.TheNestedClassANestedProperty この場合。正直なところ、実際のクラス名を使用していると、見た目が醜くなりません。

14
Jag

さらに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));
_
5
Alex M

これが以前のソリューションに付加価値をもたらすかどうかはわかりませんが、2段階のマッピングとして行うことができます。親と子の間に名前の競合がある場合は、正しい順序でマップするように注意してください(最後の勝ち)。

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

        var flattened = new Flattened();
        Mapper.Map(root, flattened);
        Mapper.Map(root.TheNestedClass, flattened);
3
toree

私は同様の問題を解決するために拡張メソッドを書きました:

_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(プロファイル)でも使用できます
1
Alexey Merson

別の答えを改善するには、両方のマッピングにMemberList.Sourceを指定し、ネストされたプロパティを無視するように設定します。その後、検証はOKに合格します。

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
Marek Toman