1

I have a source object which contains 2 references to the same collection. If I map the source type to a structurally-equivalent target type, AutoMapper will create two instances of the collection in the target instance.

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; }
}

If I create a SourceThing which has two references to the same List, map it to a TargetThing, the result is a TargetThing with two separate instances of the collection.

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
}

Is this meant to be the default mapping behavior for concrete collections in AutoMapper? Through testing, I realized that if I mapped List<int> to List<int>, I achieve the behavior I want, but I don't understand why. If AutoMapper tracks references and doesn't re-map a mapped object, wouldn't it see that the source.MoreNumbers points to the same list as source.Numbers, and set the target accordingly?

Morteza Tourani
  • 3,506
  • 5
  • 41
  • 48
drew
  • 306
  • 2
  • 11
  • It sounds like you're having the same problem described in this Stack Overflow question: [AutoMapper, how to keep references between mapped objects?](http://stackoverflow.com/questions/16666539/automapper-how-to-keep-references-between-mapped-objects). They solved this problem using a custom ITypeConverter. It sounds like that may be a lot of work in your scenario (if you're having to do this for numerous classes). – Josh Darnell Aug 13 '15 at 17:43
  • Well it's related, but his question is about preserving references across *multiple* mapping operations, while my question is within a single map(). This isn't the intention of AutoMapper, and how could it be, when each Mapper.Map() call depends only on the static mapping configuration? – drew Aug 13 '15 at 18:09

2 Answers2

4

I did some more research and tinkering. Internally, as the mapping engine walks the object graph, it chooses the best mapper for each source type/destination type. Unless there is a non-standard mapping (oversimplified), the engine will next look for a registered mapper for source and destination type. If it finds one, it creates the destination object, then traverses and maps all of the properties. It also places that destination object into the ResolutionContext.InstanceCache, which is a Dictionary<ResolutionContext, object>. If the same source object is encountered again in the same root mapping call, it'll pull the object from the cache, instead of wasting time to re-map.

However, if there is no registered mapper, the engine chooses the next applicable mapper, which in this case is the AutoMapper.Mappers.CollectionMapper. The collection mapper creates a destination collection, enumerates the source collection and maps each element. It does not add the destination object into the cache. This is clearly the design.

Resolution Context

What I find really interesting is how objects are cached in the InstanceCache. The key is the current ResolutionContext, which contains the source and destination type and the source value. ResolutionContext overrides GetHashCode() and Equals(), which use the underlying source value's same methods. I can define equality on a custom class such that a source collection with multiple equal but distinct instances of that class maps to a collection with multiple references to the same instance.

This class:

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();
    }
}

Map a collection with 2 equal (but separate) things and the result is a collection with 2 pointers to the same thing!

    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]);
    }

Preserve References

I, for one, wonder why the default behavior of AutoMapper wouldn't be to map an object graph as closely as possible to the destination structure. N source objects results in N destination objects. But since it doesn't, I'd love to see an option on the Map method to PreserveReferences like a serializer would. If that option was picked, then every reference that is mapped is placed in a Dictionary using a reference equality comparer and source object for the key and the destination as the value. Essentially, if something is already mapped, the result object of that map is used.

drew
  • 306
  • 2
  • 11
1

There is nothing wrong with the behavior, it is just how automapper maps.

In the top section, you create a list of numbers and then apply it to a second list of numbers. You can then compare and they are the same because the object has 2 pointers to the same list. It did not copy the numbers, it simply made a new reference, just like you asked.

Now, move to the automapper. It runs through and maps from one object to an equivalent object. It maps each of the properties separately, copying the information. So, even though the source has morenumbers as a pointer to the same list, automapper maps each individually. Why? It is mapping properties, not examining the property pointers. And, you would not want it to do this in most instances.

Does this make sense?

If the ultimate goal is to get a test passing, the question is not "do numbers and more numbers point at the same object", but rather "do numbers and more numbers contain the exact same list". In the first instance, the answer to both is yes, as there is a single object (list) for numbers and more numbers. In the second, the answer is false, then true, as the list is equivalent, but it does not point to the same exact object.

If you truly want it to be the same object, you will have to play the game a bit differently. If you simply want to know if the list has the same elements, then change the assertion.

Gregory A Beamer
  • 16,870
  • 3
  • 25
  • 32
  • Does it make sense? Not to me. I was careful to not describe the behavior as wrong. Instead of a List, if this was another custom reference-type, and two properties referred to the same thing, AutoMapper would only call the target constructor once, and it would result in a target object that matches the source. So while it is just mapping properties, it certainly is looking at references. And for the record, I *do* want it to do this. It maps the way I'd expect it if I map List to List. – drew Aug 13 '15 at 17:06
  • @drew YOU may want it to do it, but, in general, objects handle states, not references. When you look at an object, the important part is each of the properties have a state that can be checked. So when mapping, you are copying state. It copies numbers. It copies more numbers separate. Now, in some instances, it will examine references, but not once you nest deeply. What you have is a fringe case. I would love to see the business justification for maintaining two different properties to a single object reference, as I don't get it. Can you explain? – Gregory A Beamer Aug 13 '15 at 17:09
  • My ultimate goal is not to get a test passing. Ultimately, I'm trying to reduce the amount of redundant data traveling across the wire. We map some complex DTO's to flattened models before serializing and sending across the wire, and I'm trying to understand how to best utilize AutoMapper. If an object contains a collection of hundreds of items and each refers to an identical collection of value types, the DTO is fairly small. After mapping, each child's list is a separate instance, so the serialized object balloons out of control. – drew Aug 13 '15 at 17:14
  • Oh, I see it now. The example was a bit oversimplified so I was not thinking about a deeper hierarchy. One option is to have your DTOs set up differently than the client models. Separate out the lists from the child objects and then include mapping ids in the child. I had to do this in a web service that served up an entire catalog (no, I did not architect it, as I think entire catalog over ReST is a bit stupid). Another, if possible, is have the original call pull the summary and then only fill in what is needed of hierarchy in subsequent calls. Will that work? – Gregory A Beamer Aug 13 '15 at 17:19
  • I'm working with (against?) an overzealous architect who is unwilling to change the shared models, and equally unwilling to consider any pre/post-mapping arrangement of data that can't be done in AutoMapper. We've already overcomplicated the entire mapping layer, IMO, so we can have one-line controller methods. – drew Aug 13 '15 at 17:32
  • Architect? Part of architecture is understanding constraints, which is not evident in this question. Abstraction at the sake of a huge rise in underlying complexity is not a good option. Want me to smack him upside the head for you? ;-) – Gregory A Beamer Aug 13 '15 at 17:34