【发布时间】:2017-01-19 23:51:23
【问题描述】:
我正在尝试使用 (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; }
...
}
我真正不知道的是如何以一种不包含大量代码的方式将其映射到 Customer 实体类,只是为了找出哪些 Persons 已被添加/更新/删除等。
我已经使用 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 配置,而不必在每次使用映射器(或任何其他需要复杂映射代码的解决方案)时使用 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有什么问题? -
如果你有一个复杂的数据结构,将它映射到完全不同的东西通常会很复杂。但是,您应该只需要配置一次 AutoMapper 映射。
-
使用 AutoMapper 从实体到 DTO 的映射非常简单,但是当有集合等时,从 DTO 到 EF Core 托管实体变得非常复杂和乏味。@Sampath 这不是 AutoMapper 的直接问题,它做它应该做的很好,但我不确定在这种情况下它是否是适合这项工作的工具。这不是我需要配置 AutoMapper 的次数,而是需要配置的数量,但正如 Mike Brind 所说,这可能是一个比我“感觉”应该的更复杂的问题。无论如何,找不到任何“最佳实践”。
-
我们如何通过上面的简单示例查看您的复杂映射?您必须在此处添加更多代码。
-
@Sampath 你可能误解了我的意思,我不是在谈论特定的复杂映射,我只是指一般的复杂对象图,例如具有 OrderLines 等订单的客户。因此,它不是需要映射的超级复杂或复杂的图形,而只是 AutoMapper 是否是一个好的解决方案或是否有其他更好的选择的一些一般指导。也许 AutoMapper 是完成这项工作的最佳工具?
标签: c# asp.net-core entity-framework-core automapper asp.net-core-webapi