AutoMapper不更新集合项



我尝试使用AutoMapper将模型映射到dto。第一次尝试使用EF Core,但我能够消除EF Core并在没有它的情况下复制它

我再现了这个演示中的行为。(这里有使用EF Core的旧DEMO。(

TL;DR

这是行不通的:

var container = new Container("Container-Id 000", new List<Item> { new Item("Item-Id 000") { Name = "Item-Name" } });
var containerModel = mapper.Map<ContainerModel>(container);
// apply changes
container.Items[0].Name += " -- changed";
// update model
mapper.Map(container, containerModel);
// at this point the item does not contain the correct name:
container.Items[0].Name != containerModel.Items[0].Name   !!!!!

详细解释:

Dto和型号具有以下结构:

Container
+ Id: string { get; }
+ Items: IReadOnlyList<Item> { get; }
Item
+ Id: string { get; }
+ Name: string { get; set; }
ContainerModel
+ Id: string { get; set; }
+ Items: List<ItemModel> { get; set; }
ItemModel
+ Id: string { get; set; }
+ Name: string { get; set; }

AutoMapper配置是(也许这就是我遗漏的地方(:

var config = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<Container, ContainerModel>(MemberList.None)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dst => dst.Items, opt => opt.MapFrom(src => src.Items));
cfg.CreateMap<ContainerModel, Container>(MemberList.None)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dst => dst.Items, opt => opt.MapFrom(src => src.Items));
cfg.CreateMap<IReadOnlyList<Item>, List<ItemModel>>(MemberList.None)
.ConstructUsing((src, ctx) => src.Select(ctx.Mapper.Map<ItemModel>).ToList());
cfg.CreateMap<List<ItemModel>, IReadOnlyList<Item>>(MemberList.None)
.ConstructUsing((src, ctx) => src.Select(ctx.Mapper.Map<Item>).ToList().AsReadOnly());
cfg.CreateMap<Item, ItemModel>(MemberList.None)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.Name));
cfg.CreateMap<ItemModel, Item>(MemberList.None)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.Name));
});
var result = config.CreateMapper();
result.ConfigurationProvider.AssertConfigurationIsValid();
return result;

我创建了一个dto实例,并成功地将它们映射到模型中。(我还测试了从模型返回dto的方法,该方法也有效,但不需要重现问题。(

var mapper = CreateMapper();
var container = new Container("Container-Id 000", new List<Item> { new Item("Item-Id 000") { Name = "Item-Name" } });
var containerModel = mapper.Map<ContainerModel>(container);
// apply changes
container.Items[0].Name += " -- changed";
// update model
mapper.Map(container, containerModel);
Console.WriteLine($"Src.Name: {container.Items[0].Name}");
Console.WriteLine($"Dst.Name: {containerModel.Items[0].Name}");
if (container.Items[0].Name != containerModel.Items[0].Name)
{
throw new InvalidOperationException("The names of dto and model doesn't match!");
}

在抛出异常之前打印的输出显示了问题:

Src.Name: Item-Name -- changed
Dst.Name: Item-Name

指定的异常被抛出,但不应该(在我看来(。

我认为问题出在mapper.Map(readContainer, readContainerModel);上。我指定了一个相等比较来帮助AutoMapper找到正确的实例,但没有运气。

我在这里错过了什么?我该怎么做才能解决这个问题?

所有这些持久性代码都封装在一个小框架中,对我的同事来说应该是透明的。他们所要做的就是指定dto、模型和映射配置文件。该框架不知道";导航";。是的,我能够创建代码来分析模型类型的所有导航,并尝试找到一个等效的dto和foreach所有属性,并手动更新所有实例。但这种接缝可以治疗很多疼痛和错误,这就是我尝试自动映射的原因。

为什么我需要mapper.Map(src, dst)

所有这些都与EF Core和我的同事们的一个小型持久性框架一起工作。我尝试使用Persist()InsertOrUpdate(这是首选方法(,但我发现AutoMapper.Collection的问题报告。InsertOrUpdate-方法已损坏。在问题解决之前,我一直在尝试使用指定的解决方法,但它并不能解决问题。

我还发现一篇文章《为什么使用Automater和实体框架将DTOS映射到实体是可怕的》包含了同样的技巧。我不关心AutoMapper将为每个集合项生成的已创建模型实例。我也不容易将DbContext转发到映射函数。

我发现了问题。我添加这个是为了将集合映射到AutoMapper配置:

cfg.CreateMap<IReadOnlyList<Item>, List<ItemModel>>(MemberList.None)
.ConstructUsing((src, ctx) => src.Select(ctx.Mapper.Map<ItemModel>).ToList());
cfg.CreateMap<List<ItemModel>, IReadOnlyList<Item>>(MemberList.None)
.ConstructUsing((src, ctx) => src.Select(ctx.Mapper.Map<Item>).ToList().AsReadOnly());

它认为这将阻止自动映射器正常工作。

解决方案是删除两条打印线,并添加以下内容:

cfg.AddCollectionMappers();

我添加显式集合映射是因为我低估了AddCollectionMappers的功能,因为我使用了IReadOnlyList<>IReadOnlyDictionary<,>的不可变对象和接口,我错误地认为AutoMapper无法处理这一点。我的错。

请参阅正在运行的DEMO。

最新更新