EFコア付きASP.NETコア - DTOコレクションマッピング

asp.net-core asp.net-core-webapi automapper entity-framework-core

質問

私は、データソースとしてEFコアコンテキストを使用してJavaScriptからASP.NETコア(Web API)に子オブジェクトのコレクションを持つDTOオブジェクトを(POST / PUT)使用しようとしています。

主なDTOクラスは次のようなものです( もちろん簡略化されています ):

public class CustomerDto {
    public int Id { get;set }
    ...
    public IList<PersonDto> SomePersons { get; set; }
    ...
}

私が実際には分かっていないのは、これをCustomerエンティティクラスにどのようにマップするかです。これは、どのPersonが追加/更新/削除されたかを調べるためだけのコードが多く含まれていません。

私はAutoMapperでちょっと遊んだことがありますが、このシナリオ(複雑なオブジェクト構造)とコレクションでEF Coreでうまくいくようには見えません。

このことについていくつかのアドバイスを求めてグーグル・グーグルを務めた後、私は良いアプローチがどれほど有益なものであるかを知りませんでした。私の質問は、基本的には、「複雑な」DTOを使用しないようにJSクライアントを再設計するか、DTOとEntityモデルの間のマッピングレイヤーによって処理する必要があるか、知っている?

私は、AutoMapperとオブジェクト間の手動マッピングによって解決することができましたが、解決策のどれもすぐには感じられず、多くの定型コードでかなり複雑になりました。

編集:

次の記事では、AutoMapperとEF Coreに関して私が何を言及しているかについて説明します。複雑なコードではありませんが、これを管理するための最良の方法かどうかを知りたいだけです。

(記事のコードは上のコード例に合わせて編集されています)

http://cpratt.co/using-automapper-mapping-instances/

var updatedPersons = new List<Person>();
foreach (var personDto in customerDto.SomePersons)
{
    var existingPerson = customer.SomePersons.SingleOrDefault(m => m.Id == pet.Id);
    // No existing person with this id, so add a new one
    if (existingPerson == null)
    {
        updatedPersons.Add(AutoMapper.Mapper.Map<Person>(personDto));
    }
    // Existing person found, so map to existing instance
    else
    {
        AutoMapper.Mapper.Map(personDto, existingPerson);
        updatedPersons.Add(existingPerson);
    }
}
// Set SomePersons to updated list (any removed items drop out naturally)
customer.SomePersons = updatedPersons;

上のコードは一般的な拡張メソッドとして書かれています。

public static void MapCollection<TSourceType, TTargetType>(this IMapper mapper, Func<ICollection<TSourceType>> getSourceCollection, Func<TSourceType, TTargetType> getFromTargetCollection, Action<List<TTargetType>> setTargetCollection)
    {
        var updatedTargetObjects = new List<TTargetType>();
        foreach (var sourceObject in getSourceCollection())
        {
            TTargetType existingTargetObject = getFromTargetCollection(sourceObject);
            updatedTargetObjects.Add(existingTargetObject == null
                ? mapper.Map<TTargetType>(sourceObject)
                : mapper.Map(sourceObject, existingTargetObject));
        }
        setTargetCollection(updatedTargetObjects);
    }

.....

        _mapper.MapCollection(
            () => customerDto.SomePersons,
            dto => customer.SomePersons.SingleOrDefault(e => e.Id == dto.Id),
            targetCollection => customer.SomePersons = targetCollection as IList<Person>);

編集:

私が本当に欲しいのは、マッパー(またはマッピングコードを複雑にする必要のある他のソリューション)を使用するたびに、MapCollection()拡張機能を使用する必要がないAutoMapperの構成を1か所(プロファイル)でdelcareすることです。

そこで私はこのような拡張メソッドを作成しました

public static class AutoMapperExtensions
{
    public static ICollection<TTargetType> ResolveCollection<TSourceType, TTargetType>(this IMapper mapper,
        ICollection<TSourceType> sourceCollection,
        ICollection<TTargetType> targetCollection,
        Func<ICollection<TTargetType>, TSourceType, TTargetType> getMappingTargetFromTargetCollectionOrNull)
    {
        var existing = targetCollection.ToList();
        targetCollection.Clear();
        return ResolveCollection(mapper, sourceCollection, s => getMappingTargetFromTargetCollectionOrNull(existing, s), t => t);
    }

    private static ICollection<TTargetType> ResolveCollection<TSourceType, TTargetType>(
        IMapper mapper,
        ICollection<TSourceType> sourceCollection,
        Func<TSourceType, TTargetType> getMappingTargetFromTargetCollectionOrNull,
        Func<IList<TTargetType>, ICollection<TTargetType>> updateTargetCollection)
    {
        var updatedTargetObjects = new List<TTargetType>();
        foreach (var sourceObject in sourceCollection ?? Enumerable.Empty<TSourceType>())
        {
            TTargetType existingTargetObject = getMappingTargetFromTargetCollectionOrNull(sourceObject);
            updatedTargetObjects.Add(existingTargetObject == null
                ? mapper.Map<TTargetType>(sourceObject)
                : mapper.Map(sourceObject, existingTargetObject));
        }
        return updateTargetCollection(updatedTargetObjects);
    }
}

それで私がマッピングを作成するとき私は私たちのようにこのように:

    CreateMap<CustomerDto, Customer>()
        .ForMember(m => m.SomePersons, o =>
        {
            o.ResolveUsing((source, target, member, ctx) =>
            {
                return ctx.Mapper.ResolveCollection(
                    source.SomePersons,
                    target.SomePersons,
                    (targetCollection, sourceObject) => targetCollection.SingleOrDefault(t => t.Id == sourceObject.Id));
            });
        });

マッピングするときにこのように使うことができます:

_mapper.Map(customerDto, customer);

そして、リゾルバはマッピングを処理します。

人気のある回答

AutoMapperは最善の解決策です。

あなたはこのように非常に簡単にそれをすることができます:

    Mapper.CreateMap<Customer, CustomerDto>();
    Mapper.CreateMap<CustomerDto, Customer>();

    Mapper.CreateMap<Person, PersonDto>();
    Mapper.CreateMap<PersonDto, Person>();

注: AutoMapperList<Person>List<PersonDto>自動的にマッピングします。それらはsame nameを持ち、 PersonからPersonDtoへのマッピングはすでに存在するからPersonDto

あなたがASP.netコアにそれを注入する方法を知る必要があるなら、あなたはこの記事を見なければなりません: ASP.NET Core DIとAutoMapperの統合

DTOとエンティティ間の自動マッピング

属性と拡張メソッドを使ったマッピング



Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ