我正在使用 AutoMapper 将 UI 模型转换为 POCO,稍后我使用 DataContractSerializer 将其序列化为 XML,以保留它们之间的引用。
问题来了,在映射时,这些实体之间的引用丢失了。
UI 类相互引用,但映射过程为每个引用创建新实例,因此原始关系被破坏:(
让我解释一下:
我有 2 个 Person 类型的实体
Person
{
List<House> OwnedHouses
}
还有这两个物体
约翰 谁拥有
会 谁也拥有
当 AutoMapper 正确映射每个 Person 时,但它也将 House1 映射为两个不同的实例!!
所以我有两份 House1。约翰拥有他的 House1 (#1),威尔拥有他的 House1 (#2)。
他们不再联系了。
有什么办法可以维持原来存在的关系吗?
谢谢。
编辑:实际上我所拥有的是这样的:
文档包含子文档列表。每个 ChildDocument 都有一个可设计项列表(矩形、直线、椭圆...)和一个名为 ChildDocumentAdapter 的特殊可设计项,其中包含另一个 ChildDocument。这就是麻烦了,它可以引用另一个ChildDocument。
如果我理解这个问题,那么您正在执行两个单独的映射操作 - 一个用于约翰,另一个用于威尔。
@Sunny 是对的。 AutoMapper 并不是为此而设计的。 您对
Mapper.Map()
进行的每次调用通常都是独立于其他调用的。 通过使用 HouseListConverter 的同一实例,您可以获得在字典中缓存所有映射房屋的好处。 但是您必须全局注册它,或者将其作为选项传递给您想要分组在一起的映射调用。 这不仅仅是额外的工作,它还隐藏了转换器深处非常重要的实现细节。
如果您在一个操作中映射 John 和 Will,通过将它们放入一个集合中,输出将是您想要的,而不需要自定义转换器或解析器。
对于其他有类似问题的人来说,这可能是一个更简单的选择。
public void MapListOfPeopleWithSameHouse()
{
Mapper.CreateMap<Person, PersonDTO>();
Mapper.CreateMap<House, HouseDTO>();
var people = new List<Person>();
var house = new House() { Address = "123 Main" };
people.Add(new Person() { Name = "John", Houses = new List<House>() { house } });
people.Add(new Person() { Name = "Will", Houses = new List<House>() { house } });
var peopleDTO = Mapper.Map<List<PersonDTO>>(people);
Assert.IsNotNull(peopleDTO[0].Houses);
Assert.AreSame(peopleDTO[0].Houses[0], peopleDTO[1].Houses[0]);
}
虽然 Automapper 的设计并未考虑到这一点,但它的功能足够强大,可以使用 自定义类型转换器 来实现这一点。您需要创建自己的从
IList<House>
到 IList<HouseDto>
的转换器,并使用工厂注入它:
using System;
using System.Collections.Generic;
using AutoMapper;
using NUnit.Framework;
using SharpTestsEx;
namespace StackOverflowExample
{
public class House
{
public string Address { get; set; }
}
public class Person
{
public IList<House> OwnedHouse { get; set; }
}
public class HouseDto
{
public string Address { get; set; }
}
public class PersonDto
{
public IList<HouseDto> OwnedHouse { get; set; }
}
[TestFixture]
public class AutomapperTest
{
public interface IHouseListConverter : ITypeConverter<IList<House>, IList<HouseDto>>
{
}
public class HouseListConverter : IHouseListConverter
{
private readonly IDictionary<House, HouseDto> existingMappings;
public HouseListConverter(IDictionary<House, HouseDto> existingMappings)
{
this.existingMappings = existingMappings;
}
public IList<HouseDto> Convert(ResolutionContext context)
{
var houses = context.SourceValue as IList<House>;
if (houses == null)
{
return null;
}
var dtos = new List<HouseDto>();
foreach (var house in houses)
{
HouseDto mapped = null;
if (existingMappings.ContainsKey(house))
{
mapped = existingMappings[house];
}
else
{
mapped = Mapper.Map<HouseDto>(house);
existingMappings[house] = mapped;
}
dtos.Add(mapped);
}
return dtos;
}
}
public class ConverterFactory
{
private readonly IHouseListConverter resolver;
public ConverterFactory()
{
resolver = new HouseListConverter(new Dictionary<House, HouseDto>());
}
public object Resolve(Type t)
{
return t == typeof(IHouseListConverter) ? resolver : null;
}
}
[Test]
public void CustomResolverTest()
{
Mapper.CreateMap<House, HouseDto>();
Mapper.CreateMap<IList<House>, IList<HouseDto>>().ConvertUsing<IHouseListConverter>();
Mapper.CreateMap<Person, PersonDto>();
var house = new House {Address = "any"};
var john = new Person {OwnedHouse = new List<House> {house}};
var will = new Person { OwnedHouse = new List<House> { house } };
var converterFactory = new ConverterFactory();
var johnDto = Mapper.Map<PersonDto>(john, o=>o.ConstructServicesUsing(converterFactory.Resolve));
var willDto = Mapper.Map<PersonDto>(will, o=>o.ConstructServicesUsing(converterFactory.Resolve));
johnDto.OwnedHouse[0].Should().Be.SameInstanceAs(willDto.OwnedHouse[0]);
johnDto.OwnedHouse[0].Address.Should().Be("any");
}
}
}
就我而言(.NET 8;AutoMapper 13.0.1),我必须选择通过将 .PreserveReferences() 添加到 CreateMap 调用来保留引用。示例:
var Mapper = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Aircraft, AircraftDTO>().PreserveReferences();
cfg.CreateMap<AircraftModel, AircraftModelDTO>().PreserveReferences();
cfg.CreateMap<AircraftType, AircraftTypeDTO>().PreserveReferences();
}).CreateMapper();
在 AutoMapper 文档中,他们指出您需要选择加入:
2.7 循环引用 以前,AutoMapper 可以通过跟踪映射的内容来处理循环引用,并且在每个映射上, 检查源/目标对象的本地哈希表以查看该项目是否已映射。事实证明这个跟踪 非常昂贵,并且您需要选择使用 PreserveReferences 才能使圆形地图正常工作。或者,您可以 配置最大深度
如果我删除“PreserveReferences”,则“Assert.ReferenceEquals”对于相同对象返回 false,但使用它,结果为 true。