web-dev-qa-db-ja.com

AutoMapperを使用してDTOを展開解除する

AutoMapperを使用してDTOからドメインオブジェクトに移動する時間を節約しようとしましたが、機能するようにマップを構成するのに問題があり、AutoMapperが仕事。

ドメインオブジェクトのこの例(1つのエンティティと1つの値)を考えてみます。

public class Person
{
    public string Name { get; set; }
    public StreetAddress Address { get; set; }
}

public class StreetAddress
{
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

私のDTO(Linq-to-SQLオブジェクトから)は、おおよそ次のように表示されます。

public class PersonDTO
{
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

私のリポジトリでこれを行うことができるようにしたいです:

return Mapper.Map<PersonDTO, Person>(result);

AutoMapperをあらゆる方法で構成しようとしましたが、一般的なタイプマップ構成がないか、サポートされていないマッピングエラーが発生し続けます。どこに失敗したのかを示す詳細はありません。

私はいくつかの異なる設定を試しましたが、ここにいくつかあります:

Mapper.CreateMap<PersonDTO, Person>()
    .ForMember(dest => dest.Address, opt => opt.MapFrom(Mapper.Map<Person, Domain.StreetAddress>));

そして

Mapper.CreateMap<Person, Domain.Person>()
    .ForMember(dest => dest.Address.Address1, opt => opt.MapFrom(src => src.Address))
    .ForMember(dest => dest.Address.City, opt => opt.MapFrom(src => src.City))
    .ForMember(dest => dest.Address.State, opt => opt.MapFrom(src => src.State));

AutoMapperを使用したflatteningオブジェクトは簡単ですが、unflatteningと読みましたそれらは簡単ではありません...あるいは可能です。誰かが私が不可能なことをしようとしているのか、私が間違っているのかどうかを教えてもらえますか?

実際のオブジェクトはもう少し複雑なので、エラーの鍵となる情報を省略している可能性があることに注意してください...もし私がしていることが正しいようであれば、より多くの情報を提供したり、テストのためにオブジェクトを簡略化したりできます。

39
Josh Anderson

https://github.com/omuleanu/ValueInjecter を使用すると、flatteningおよびunflatteningおよびその他必要なものは、ダウンロードにasp.net mvcサンプルアプリケーションがありますすべての機能が示されます(単体テストも)

8
Omu

これも私にとってはうまくいくようです:

Mapper.CreateMap<PersonDto, Address>();
Mapper.CreateMap<PersonDto, Person>()
        .ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));

基本的に、dtoから両方のオブジェクトへのマッピングを作成し、それを子オブジェクトのソースとして使用します。

66
sydneyos

シドニーの回答に加えて、Trevor de Koekkoekのコメントによると、この方法で2方向のマッピングが可能です

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

public class PersonViewModel
{
    public string Name { get; set; }
    public string AddressStreet { get; set; }
    public string AddressCity { get; set; }
    public string AddressState { get; set; }
}

オートマッパーマッピング

Mapper.Initialize(cfg => cfg.RecognizePrefixes("Address"));
Mapper.CreateMap<Person, PersonViewModel>();
Mapper.CreateMap<PersonViewModel, Address>();
Mapper.CreateMap<PersonViewModel, Person>()
    .ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));

NameOf クラスを実装すると、接頭辞のマジック文字列を取り除くことができます

Mapper.Initialize(cfg => cfg.RecognizePrefixes(Nameof<Person>.Property(x => x.Address)));

編集:C#6の場合

Mapper.Initialize(cfg => cfg.RecognizePrefixes(nameof(Person.Address)));
9
sanjuro

コメントを投稿できないため、回答を投稿します。 AutoMapperの実装にいくつかの変更があったと思いますので、回答 https://stackoverflow.com/a/5154321/2164198 HansoSによって提案されたものはコンパイルできなくなりました。そのようなシナリオで使用できる別の方法があります-ResolveUsing

Mapper.CreateMap<Person, Domain.Person>()
    .ForMember(dest => dest.Address, opt => opt.ResolveUsing( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
9
Ivan Samygin

これは遅いかもしれませんが、ラムダ式を使用して次のようなオブジェクトを作成することで解決できます。

Mapper.CreateMap<Person, Domain.Person>()
        .ForMember(dest => dest.Address, opt => opt.MapFrom( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
5
HansoS

別の解決策があります。主なアイデアは、AutoMapper know 平坦化されたオブジェクトのプロパティに適切な名前を付けるときに、ネストされたオブジェクトを平坦化する方法です。接頭辞としての名前。あなたの場合Addressはプレフィックスです:

public class PersonDTO
{
    public string Name { get; set; }
    public string AddressCity { get; set; }
    public string AddressState { get; set; }
    ...
}

したがって、ネストされたものから平坦化されたものへの使い慣れたマッピングを作成し、ReverseMapメソッドを使用して、AutomMapperはネストされたオブジェクトを平坦化解除する方法を理解できます。

CreateMap<Person, PersonDTO>()
   .ReverseMap();

それで全部です!

1
Andrei

私はこれを使っています

public static void Unflatten<TSource, TDestination, TMember>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt)
{
    var prefix = opt.DestinationMember.Name;
    var memberProps = typeof(TMember).GetProperties();
    var props = typeof(TSource).GetProperties().Where(p => p.Name.StartsWith(prefix))
        .Select(sourceProp => new
        {
            SourceProp = sourceProp,
            MemberProp = memberProps.FirstOrDefault(memberProp => prefix + memberProp.Name == sourceProp.Name)
        })
        .Where(x => x.MemberProp != null);
    var parameter = Expression.Parameter(typeof(TSource));

    var bindings = props.Select(prop => Expression.Bind(prop.MemberProp, Expression.Property(parameter, prop.SourceProp)));
    var resolver = Expression.Lambda<Func<TSource, TMember>>(
        Expression.MemberInit(Expression.New(typeof(TMember)), bindings),
        parameter);

    opt.ResolveUsing(resolver.Compile());
}

構成

new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Person, PersonDTO>();
    cfg.CreateMap<PersonDTO, Person>().ForMember(x => x.HomeAddress, opt => opt.Unflatten());
});

モデル

public class Person
{
    public string Name { get; set; }
    public Address HomeAddress { get; set; }
}

public class Address
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

AutoMapperの平坦化規則に従う

public class PersonDTO
{
    public string Name { get; set; }
    public string HomeAddressLine1 { get; set; }
    public string HomeAddressLine2 { get; set; }
    public string HomeAddressCity { get; set; }
    public string HomeAddressState { get; set; }
    public string HomeAddressZipCode { get; set; }
}

おそらく多くの改善が必要ですが、うまくいきます...

1
andres.chort