带有 EF Core 的 ASP.NET Core - DTO 集合映射 [英] ASP.NET Core with EF Core - DTO Collection mapping

查看:75
本文介绍了带有 EF Core 的 ASP.NET Core - DTO 集合映射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to use (POST/PUT) a DTO object with a collection of child objects from JavaScript to an ASP.NET Core (Web API) with an EF Core context as my data source.

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

The main DTO class is something like this (simplified of course):

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

我真正不知道的是如何以一种不包含大量代码的方式将其映射到 Customer 实体类,只是为了找出已添加/更新/删除了哪些人员等.

What I don't really know is how to map this to the Customer entity class in a way that does not include a lot of code just for finding out which Persons had been added/updated/removed etc.

我玩过 AutoMapper,但在这种情况下(复杂的对象结构)和集合,它似乎并不适合 EF Core.

I have played around a bit with AutoMapper but it does not really seem to play nice with EF Core in this scenario (complex object structure) and collections.

在谷歌上搜索了一些关于这方面的建议后,我还没有找到关于什么是好的方法的任何好的资源.我的问题基本上是:我应该重新设计 JS 客户端以不使用复杂"的 DTO,还是应该"由我的 DTO 和实体模型之间的映射层处理,或者是否有任何其他我没有的好的解决方案知道吗?

After googling for some advice around this I haven't found any good resources around what a good approach would be. My questions is basically: should I redesign the JS-client to not use "complex" DTOs or is this something that "should" be handled by a mapping layer between my DTOs and Entity model or are there any other good solution that I am not aware of?

我已经能够使用 AutoMapper 和通过在对象之间手动映射来解决它,但是没有一个解决方案感觉正确并且很快变得非常复杂,并且包含大量样板代码.

以下文章描述了我所指的有关 AutoMapper 和 EF Core 的内容.它的代码并不复杂,但我只想知道这是否是管理此问题的最佳"方式.

The following article describes what I am referring to regarding AutoMapper and EF Core. Its not complicated code but I just want to know if it's the "best" way to manage this.

(文章中的代码经过编辑以适合上面的代码示例)

(Code from the article is edited to fit the code example above)

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;

上面的代码是作为通用扩展方法编写的.

Code above written as a generic extension method.

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 配置放在一个地方(配置文件),而不必在每次使用映射器(或任何其他需要使映射代码复杂化的解决方案)时使用 MapCollection() 扩展.

One thing I really want is to delcare the AutoMapper configuration in one place (Profile) not have to use the MapCollection() extension every time I use the mapper (or any other solution that requires complicating the mapping code).

所以我创建了一个这样的扩展方法

So I created an extension method like this

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

然后当我创建映射时,我是这样使用的:

Then when I create the mappings I us it like this:

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

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

Which allow me to use it like this when mapping:

_mapper.Map(customerDto, customer);

解析器负责映射.

推荐答案

AutoMapper 是最好的解决方案.

你可以很容易地这样做:

You can do it very easily like this :

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

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

注意:因为AutoMapper会自动将List映射到List.since他们有同名,并且已经有一个从PersonPersonDto的映射.

Note : Because AutoMapper will automatically map the List<Person> to List<PersonDto>.since they have same name, and there is already a mapping from Person to PersonDto.

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

If you need to know how to inject it to ASP.net core,you have to see this article : Integrating AutoMapper with ASP.NET Core DI

DTO 和实体之间的自动映射

使用属性和扩展方法的映射

这篇关于带有 EF Core 的 ASP.NET Core - DTO 集合映射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆