为什么Automapper在每次引用源集合时将一个具体集合映射到一个新实例?



我有一个源对象,其中包含对同一集合的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。集合映射器创建目标集合,枚举源集合并映射每个元素。它将目标对象添加到缓存中。这显然是设计。

<标题>决议上下文h1> 发现真正有趣的是对象如何在InstanceCache中缓存。关键字是当前的ResolutionContext,它包含源和目标类型以及源值。ResolutionContext覆盖GetHashCode()和Equals(),它们使用底层源值的相同方法。我可以在自定义类上定义相等性,使具有该类的多个相等但不同的实例的源集合映射到具有对同一实例的多个引用的集合。

这类:

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)用于数字和多个数字。在第二种情况下,答案是假的,然后是真的,因为列表是等价的,但它们并不指向完全相同的对象。

如果你真的希望它是相同的对象,你将不得不玩一些不同的游戏。如果您只是想知道列表是否具有相同的元素,那么更改断言。

相关内容

  • 没有找到相关文章

最新更新