ASP.NET核心與EF核心 - DTO集合映射

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

我正在嘗試使用(POST / PUT)DTO對象,其中包含從JavaScript到ASP.NET核心(Web API)的子對象集合,其中EF Core上下文作為我的數據源。

主要的DTO類是這樣的( 當然簡化 ):

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

我真正不知道的是如何將這個映射到Customer實體類的方式不包含大量代碼,只是為了找出添加/更新/刪除了哪些人員等。

我在AutoMapper上玩了很多,但在這個場景(複雜的對象結構)和集合中,它似乎與EF Core不太匹配。

谷歌搜索一些關於這方面的建議後,我沒有找到任何好的資源圍繞什麼樣的好方法。我的問題基本上是:我應該重新設計JS客戶端以不使用“複雜”DTO,還是應該由我的DTO和實體模型之間的映射層處理,或者是否有任何其他好的解決方案我不是意識到?

我已經能夠使用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>);

編輯:

我真正想要的一件事是將AutoMapper配置delcare在一個地方(Profile),每次我使用mapper(或任何其他需要復雜映射代碼的解決方案)時都不必使用MapCollection()擴展。

所以我創建了這樣的擴展方法

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

注意:因為AutoMapper會自動將List<Person>映射到List<PersonDto>因為它們具有same name ,並且已經存在從PersonPersonDto的映射。

如果你需要知道如何將它注入ASP.net核心,你必須看到這篇文章: 將AutoMapper與ASP.NET Core DI集成

DTO和實體之間的自動映射

使用屬性和擴展方法進行映射



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因