AutoMapper 不更新集合项
AutoMapper does not update Collection items
我尝试使用 AutoMapper 将模型映射到 dtos。第一次尝试使用 EF-Core,但我能够消除 EF-Core 并在没有它的情况下重现它。
我重现了这个 DEMO 中的行为。
(使用 EF-Core 的旧 DEMO 是 here。)
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-Configuration 是(也许这就是我遗漏的地方):
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 实例并将它们成功映射到模型。 (我还测试了从 model 到 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 找到正确的实例,但没有运气。
我在这里错过了什么?我该怎么做才能解决这个问题?
所有持久性代码都封装在一个小框架中,对我的同事来说应该是透明的。他们所要做的就是指定 dtos、模型和映射配置文件。该框架不知道“导航”。是的,我能够创建代码来分析模型类型的所有导航,并尝试找到等效的 dto 并 foreach 所有属性并手动更新所有实例。但这接缝可以治疗很多痛苦和错误,这就是我尝试自动映射的原因。
为什么我需要mapper.Map(src, dst)
?
所有这些都与 EF-Core 一起工作,并为我的同事提供了一个小型持久性框架。我尝试使用 Persist()
和 InsertOrUpdate
(首选方法),但我发现 issue report for AutoMapper.Collection。 InsertOrUpdate
-方法被破坏了。指定的 workarround 是我在问题解决之前尝试使用的 - 但它没有解决问题。
我还发现那篇文章 WHY MAPPING DTOS TO ENTITIES USING AUTOMAPPER AND ENTITYFRAMEWORK IS HORRIBLE 包含相同的技巧。我不关心 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());
这似乎会阻止 AutoMapper 正常工作。
解决方案是删除两个打印行并添加以下内容:
cfg.AddCollectionMappers();
我添加了显式集合映射是因为我低估了 AddCollectionMappers
的力量,因为我使用了 IReadOnlyList<>
和 IReadOnlyDictionary<,>
的不可变对象和接口,而我的观点是错误的AutoMapper 无法处理。我的错。
查看工作 DEMO。
我尝试使用 AutoMapper 将模型映射到 dtos。第一次尝试使用 EF-Core,但我能够消除 EF-Core 并在没有它的情况下重现它。
我重现了这个 DEMO 中的行为。 (使用 EF-Core 的旧 DEMO 是 here。)
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-Configuration 是(也许这就是我遗漏的地方):
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 实例并将它们成功映射到模型。 (我还测试了从 model 到 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 找到正确的实例,但没有运气。
我在这里错过了什么?我该怎么做才能解决这个问题?
所有持久性代码都封装在一个小框架中,对我的同事来说应该是透明的。他们所要做的就是指定 dtos、模型和映射配置文件。该框架不知道“导航”。是的,我能够创建代码来分析模型类型的所有导航,并尝试找到等效的 dto 并 foreach 所有属性并手动更新所有实例。但这接缝可以治疗很多痛苦和错误,这就是我尝试自动映射的原因。
为什么我需要mapper.Map(src, dst)
?
所有这些都与 EF-Core 一起工作,并为我的同事提供了一个小型持久性框架。我尝试使用 Persist()
和 InsertOrUpdate
(首选方法),但我发现 issue report for AutoMapper.Collection。 InsertOrUpdate
-方法被破坏了。指定的 workarround 是我在问题解决之前尝试使用的 - 但它没有解决问题。
我还发现那篇文章 WHY MAPPING DTOS TO ENTITIES USING AUTOMAPPER AND ENTITYFRAMEWORK IS HORRIBLE 包含相同的技巧。我不关心 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());
这似乎会阻止 AutoMapper 正常工作。
解决方案是删除两个打印行并添加以下内容:
cfg.AddCollectionMappers();
我添加了显式集合映射是因为我低估了 AddCollectionMappers
的力量,因为我使用了 IReadOnlyList<>
和 IReadOnlyDictionary<,>
的不可变对象和接口,而我的观点是错误的AutoMapper 无法处理。我的错。
查看工作 DEMO。