使用AutoMapper在列表中添加,更新和删除项目

时间:2019-01-16 15:13:07

标签: c# asp.net-core entity-framework-core automapper

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:我不是经验丰富的程序员,但对我来说似乎并不合理。

3 个答案:

答案 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更新。