我有一个源对象,其中包含对同一集合的2个引用。如果我将源类型映射到结构等效的目标类型,那么AutoMapper将在目标实例中创建集合的两个实例。
class SourceThing
{
public string Name { get; set; }
public List<int> Numbers { get; set; }
public List<int> MoreNumbers { get; set; }
}
class TargetThing
{
public string Name { get; set; }
public List<int> Numbers { get; set; }
public List<int> MoreNumbers { get; set; }
}
如果我创建一个SourceThing
,它有两个引用到同一个List,将它映射到一个TargetThing
,结果是一个TargetThing
,有两个单独的集合实例。
public void MapObjectWithTwoReferencesToSameList()
{
Mapper.CreateMap<SourceThing, TargetThing>();
//Mapper.CreateMap<List<int>, List<int>>(); // passes when mapping here
var source = new SourceThing() { Name = "source" };
source.Numbers = new List<int>() { 1, 2, 3 };
source.MoreNumbers = source.Numbers;
Assert.AreSame(source.Numbers, source.MoreNumbers);
var target = Mapper.Map<TargetThing>(source);
Assert.IsNotNull(target.Numbers);
Assert.AreSame(target.Numbers, target.MoreNumbers); // fails
}
这是否意味着在AutoMapper中具体集合的默认映射行为?通过测试,我意识到如果我将List<int>
映射到List<int>
,我实现了我想要的行为,但是我不明白为什么。如果AutoMapper跟踪引用并且不重新映射映射对象,它会不会看到source.MoreNumbers
指向与source.Numbers
相同的列表,并相应地设置目标?
我做了更多的研究和修补。在内部,当映射引擎遍历对象图时,它会为每个源类型/目标类型选择最佳映射器。除非存在非标准映射(过度简化),否则引擎接下来将为源和目标类型查找已注册的映射器。如果找到一个,它就创建目标对象,然后遍历并映射所有属性。它还将目标对象放入ResolutionContext.InstanceCache
中,这是一个Dictionary<ResolutionContext, object>
。如果在同一个根映射调用中再次遇到相同的源对象,它将从缓存中拉出该对象,而不是浪费时间重新映射。
但是,如果没有注册的映射器,则引擎选择下一个适用的映射器,在本例中是AutoMapper.Mappers.CollectionMapper
。集合映射器创建目标集合,枚举源集合并映射每个元素。它不将目标对象添加到缓存中。这显然是设计。
这类:
class EquatableThing
{
public string Name { get; set; }
public override bool Equals(object other)
{
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(null, other)) return false;
return this.Name == ((EquatableThing)other).Name;
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
映射具有两个相等(但分开)对象的集合,结果是具有指向相同对象的两个指针的集合!
public void MapCollectionWithTwoEqualItems()
{
Mapper.CreateMap<EquatableThing, EquatableThing>();
var thing1 = new EquatableThing() { Name = "foo"};
var thing2 = new EquatableThing() { Name = "foo"};
Assert.AreEqual(thing1, thing2);
Assert.AreEqual(thing1.GetHashCode(), thing2.GetHashCode());
Assert.AreNotSame(thing1, thing2);
// create list and map this thing across
var list = new List<EquatableThing>() { thing1, thing2};
var result = Mapper.Map<List<EquatableThing>, List<EquatableThing>>(list);
Assert.AreSame(result[0], result[1]);
}
<标题>保存引用我想知道为什么AutoMapper的默认行为不是将对象图尽可能地映射到目标结构。N个源对象产生N个目标对象。但由于它没有这样做,我希望在Map方法上看到一个指向PreserveReferences的选项,就像序列化器那样。如果选择了该选项,则使用引用相等比较器和源对象作为键,并将目标作为值,将映射的每个引用放入Dictionary中。从本质上讲,如果已经映射了某些内容,则使用该映射的结果对象。
标题>标题>这个行为没有错,这只是automapper映射的方式。
在顶部部分,创建一个数字列表,然后将其应用于第二个数字列表。然后你可以比较,它们是相同的,因为对象有两个指针指向同一个列表。它没有复制数字,它只是做了一个新的引用,就像你要求的。
现在,移动到自动器。它贯穿一个对象并从一个对象映射到另一个等效对象。它分别映射每个属性,复制信息。因此,即使源有更多的数字作为指向同一列表的指针,automapper也会单独映射每个数字。为什么?它是映射属性,而不是检查属性指针。而且,在大多数情况下,您不会希望它这样做。
这有意义吗?
如果最终目标是让测试通过,问题不是"do数和更多数指向同一个对象",而是"do数和更多数包含完全相同的列表"。在第一个实例中,这两个问题的答案都是肯定的,因为只有一个对象(list)用于数字和多个数字。在第二种情况下,答案是假的,然后是真的,因为列表是等价的,但它们并不指向完全相同的对象。
如果你真的希望它是相同的对象,你将不得不玩一些不同的游戏。如果您只是想知道列表是否具有相同的元素,那么更改断言。