AutoMapper是否没有本地方法来更新需要删除,添加 或更新 的实例的嵌套列表?
您好,我在带有EF Core的ASP.Net Core应用程序中使用AutoMapper,将我的API资源映射到我的模型。这对于我的大多数应用程序来说都工作正常,但是我对更新映射的嵌套列表(其中需要保留列出的实例)的解决方案不满意。我不想覆盖现有列表,我想删除传入资源中不再存在的实例,添加新实例,并更新现有实例。
模型:
public class MainObject
{
public int Id { get; set; }
public List<SubItem> SubItems { get; set; }
}
public class SubItem
{
public int Id { get; set; }
public int MainObjectId { get; set; }
public MainObject MainObject { get; set; }
public double Value { get; set; }
}
资源:
public class MainObjectResource
{
public int Id { get; set; }
public ICollection<SubItemResource> SubItems { get; set; }
}
public class SubItemResource
{
public int Id { get; set; }
public double Value { get; set; }
}
控制器:
public class MainController : Controller
{
private readonly IMapper mapper;
private readonly IMainRepository mainRepository;
private readonly IUnitOfWork unitOfWork;
public MainController(IMapper mapper, IMainRepository mainRepository, IUnitOfWork unitOfWork)
{
this.mapper = mapper;
this.mainRepository = mainRepository;
this.unitOfWork = unitOfWork;
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateMain(int id, [FromBody] MainObjectResource mainObjectResource)
{
MainObject mainObject = await mainRepository.GetMain(id);
mapper.Map<MainObjectResource, MainObject>(mainObjectResource, mainObjectResource);
await unitOfWork.CompleteAsync();
return Ok();
}
}
映射配置文件:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<MainObject, MainObjectResource>();
CreateMap<SubItem, SubItemResource>();
CreateMap<MainObject, MainObjectResource>().ReverseMap()
.ForMember(m => m.Id, opt => opt.Ignore())
.ForMember(m => m.SubItems, opt => opt.Ignore())
.AfterMap((mr, m) => {
//To remove
List<MainObject> removedSubItems = m.SubItems.Where(si => !mr.SubItems.Any(sir => si.Id == sir.Id)).ToList();
foreach (SubItem si in removedSubItems)
m.SubItems.Remove(si);
//To add
List<SubItemResource> addedSubItems = mr.SubItems.Where(sir => !m.SubItems.Any(si => si.Id == sir.Id)).ToList();
foreach (SubItemResource sir in addedSubItems)
m.SubItems.Add( new SubItem {
Value = sir.Value,
});
// To update
List<SubItemResource> updatedSubItems = mr.SubItems.Where(sir => m.SubItems.Any(si => si.Id == sir.Id)).ToList();
SubItem subItem = new SubItem();
foreach (SubItemResource sir in updatedSubItems)
{
subItem = m.SubItems.SingleOrDefault(si => si.Id == sir.Id);
subItem.Value = sir.Value;
}
});
}
}
我在这里所做的是一个自定义映射,但是我觉得这是一个通用情况,我希望AutoMapper能够通过某种扩展来处理它。我看到了一些示例,其中映射配置文件使用自定义映射(.AfterMap),但是实际的映射是由AutoMapper的静态实例完成的。我不确定这是否适合通过依赖项注入来使用AutoMapper:我不是经验丰富的程序员,但对我来说似乎并不合理。
答案 0 :(得分:2)
这个问题比表面上看起来更难解决。您很容易对列表进行自定义映射,因为您知道自己的应用程序。 AutoMapper没有。例如,是什么使源项目等于目标项目,从而使AutoMapper能够识别出它应该映射在现有项目上而不是添加? PK? PK是哪个属性?该属性在源和目标上都相同吗?这些是您可以在AfterMap
中轻松回答的问题,对于AutoMapper而言则不是很多。
因此,AutoMapper始终将集合映射为新项目。如果这不是您想要的行为,那就是AfterMap
之类的东西。还要记住,AutoMapper不是专门为与EF配合使用而设计的,这实际上是问题所在,而不是AutoMapper。 EF的更改跟踪是导致AutoMapper的默认集合映射行为出现问题的原因。在其他情况和情况下,这不是问题。
答案 1 :(得分:2)
感谢Ivan Stoev,我开始研究AutoMapper.Collection,它确实是我希望找到的扩展名。实施清单后,按照我的意愿进行更新。该配置在我的用法中很简单,因为我只需要指定对象的ID即可。
我的启动配置更改为:
using AutoMapper;
using AutoMapper.EquivalencyExpression;
[....]
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(cfg => {
cfg.AddCollectionMappers();
});
}
[....]
我的地图资料:
CreateMap<MainObject, MainObjectResource>().ReverseMap()
.ForMember(m => m.Id, opt => opt.Ignore());
CreateMap<SubItem, SubItemResource>().ReverseMap()
.EqualityComparison((sir, si) => sir.Id == si.Id);
答案 2 :(得分:1)
我相信我已经实现了一个配置,其中我的InputModels和EntityClasses可以包含一个ID,我只需要创建一个映射配置文件并调用:Mapper.Map(inputModelClassInstance, entityClassInstance);
因此,AutoMapper和EF CORE将使用实例的ID来查看它们是否相等,并按预期进行添加,删除或更新。
这是我所做的:
我用过:https://github.com/AutoMapper/Automapper.Collection.EFCore
(链接中的修改代码):
var services = new ServiceCollection();
services
.AddEntityFrameworkSqlServer() //Use your EF CORE provider here
.AddDbContext<MyContext>(); //Use your context here
services.AddAutoMapper((serviceProvider, automapper) =>
{
automapper.AddCollectionMappers();
automapper.UseEntityFrameworkCoreModel<MyContext>(serviceProvider);
}, typeof(DB).Assembly);
var serviceProvider = services.BuildServiceProvider();
然后我的CreateMap<>()
看起来像这样:
CreateMap<InputModelClass, EntityClass>(MemberList.Source);
CreateMap<ChildInputModelClass, ChildClass>(MemberList.Source);
我只是通过以下方式执行更新:
//Fetch entityClassInstance from db:
var entityClassInstance = Context.EntityClasses
.Where(ec => ex.id == id)
.Include(ec => ec.children)
.FirstOrDefault();
//Perform update:
Mapper.Map(inputModelClassInstance, entityClassInstance);
我可以从父级的子级集合中添加和删除,并且EF CORE + AutoMapper将按预期的方式添加,删除和更新。
我相信.UseEntityFrameworkCoreModel<MyContext>(serviceProvider)
添加了AutoMapper将使用ID来比较要添加,删除和更新的内容的配置。
重要提示: 重要的是,您必须包含子实体,否则它们将不会被AutoMapper更新。