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

jmw*_*jmw 11 automapper entity-framework-core asp.net-core asp.net-core-webapi

我正在尝试使用(POST/PUT)DTO对象,其中包含从JavaScript到ASP.NET Core(Web API)的子对象集合,其中EF Core上下文作为我的数据源.

主要的DTO类是这样的(当然简化):

public class CustomerDto {
    public int Id { get;set }
    ...
    public IList<PersonDto> SomePersons { get; set; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

我真正不知道的是如何以不包含大量代码的方式将其映射到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;
Run Code Online (Sandbox Code Playgroud)

以上代码编写为通用扩展方法.

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);
    }
Run Code Online (Sandbox Code Playgroud)

.....

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

编辑:

我真正想要的一件事是将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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,当我创建映射时,我就像这样:

    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));
            });
        });
Run Code Online (Sandbox Code Playgroud)

这允许我在映射时使用它:

_mapper.Map(customerDto, customer);
Run Code Online (Sandbox Code Playgroud)

解析器负责映射.

Sam*_*ath 6

AutoMapper 是最好的解决方案.

你可以很容易地做到这一点:

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

    Mapper.CreateMap<Person, PersonDto>();
    Mapper.CreateMap<PersonDto, Person>();
Run Code Online (Sandbox Code Playgroud)

注:由于AutoMapper会自动映射List<Person>List<PersonDto>.既然他们有same name,并且已经有从映射PersonPersonDto.

如果你需要知道如何将它注入ASP.net核心,你必须看到这篇文章:将AutoMapper与ASP.NET Core DI集成

DTO和实体之间的自动映射

使用属性和扩展方法进行映射

  • 说AutoMapper是最好的解决方案,就像说*<特定品牌的黄油>*是最好的黄油.使用AutoMapper可能是一个很好的解决方案.但你永远无法证明它是最好的.除此之外,AutoMapper很棒,但那只是我的拙见. (2认同)