0

I have a list of objects that are loaded from a database, let's call them 'MyObjects' then I have a list of extensions objects 'ExtensionsObjects' that are loaded from a separate database. I use automapper to map some properties of these extension objects to 'MyObjects'. (The extention object contains a key field to MyObject)

This works, the fields are mapped correctly from ExtentionObject to MyObject but the automapper returns a list that only returns those 'MyObjects' to which an 'ExtensionObject was mapped. (and a MyObject without a Extention object is a perfectly valid situation).

The code I am using for the mapping:

//first get the lists of MyObjects and ExtentionObjects
List<MyObject> myObjects = GetMyObjects(); 
List<ExtentionObject> extentionObjects = GetExtentionObjects(); 

//execute the mapping (_mapper is my automapper)
myObjects = _mapper.Map(extentionObjects, myObjects); 

//myObject now contains less objects than before the call to the mapper

The code for the automapper configuration (cfg being the mapper configuration used to create the mapper):

cfg.CreateMap<ExtentionObject, MyObject>()
    .EqualityComparison((eo, my)=> CheckForEquality(eo, my))
    .ForMember(....)
    .ForMember(....)
    .ReverseMap().ConvertUsing((mo, eo)=> 
    {
        var ext = new ExtentionObect();
        ...
        return ext;
    });

The check for equality function simply checks if the ID's of ExtentionObject and MyObject match.

I want the resulting list to contain all the items that where in the orginial 'myObjects' list. The information in the ExtentionObject instances should be added to the corresponding MyObject instances, but as the Extention is optional all 'MyObjects' must remain in the resulting list.

Say my database contains MyObjects with Keys 1, 2,3 and ExtentionObjects with Key 1 and 3.

//before this cal myObjects contains 3 items, ExentionObjects contains 2
myObjects = _mapper.Map(extentionObjects, myObjects); 
//after this cal myObjects contains only 2 items, 
//with the properties from Extentionobject 1 and 3 correctly mapped to MyObject 1 and 3, 
//ERROR => MyObject 2 has "disappeared" from the 'destination' list

The question is 'How do I keep Object 2 in my list'?

Nils
  • 59
  • 6
  • what about a custom object comparer, using `IEqualityComparer` ? – Aarif Oct 15 '19 at 11:53
  • I've tried with a custom ITypeConvertor (but there where some issues (if I remember correctly, the code was writting some time ago, testing took some time...). But wont the problem be the same? Objects not mapped from ExtensionObject will not be added (I guess) I also forgot to mention that I call ReverseMap() to be able to reverse the mapping. – Nils Oct 15 '19 at 12:01
  • https://stackoverflow.com/questions/6694508/how-to-use-the-iequalitycomparer you can get an idea from here how you would accomplish a custom comparison as needed instead of ID's comparison. – Aarif Oct 15 '19 at 12:03
  • @Aarif, thanks for your input but why would an IEqualityComparer have any other result? My EqualityCoparison works perfectly, say I have MyObjects with keys 1,2,3 and Extention objects with keys 1 and 3. The resulting list contains 2 MyObjects (1 and 3) with the properties mapped from Extention objects 1 and 3. But **MyObject 2 is missing from the resulting list**. – Nils Oct 15 '19 at 12:13
  • your question still isn't clear, "I would expect the resulting list to contain the exact same number of items", how and why this should work this way? this can't be achieved via some framework magic, you'd have to alter the comparison criteria for this – Aarif Oct 15 '19 at 12:20
  • See modification, I hope it is clear now, I want to add extra info to an existing list without loosing any objects already present in that list. – Nils Oct 15 '19 at 12:40

1 Answers1

1

After further investigation it turned out that the issue was not in Automapper itself but in Automapper.Collections (an extention to automapper to map collections). An issue for the same functionallity was raised on its github repo but there is no real solution. There is advise though on how to modify automapper.collection to avoid the deletion, but I do not think that forking this repository is wise in the long run. There is however an indiaction as to where the items are removed from the list. It uses the ICollection.Remove() method (not a clear and then adding values).

So my solution is to wrap my list in an 'EditOnlyCollection' that has a 'nop' implementation for 'Remove'. (In my case there is also a 'nop' for other methods but this is easily changed). Note: It does not work with the build in ReadOnlyCollection as that throws exceptions in the remove method.

class EditOnlyCollection<T> : ICollection<T>
    {
        private readonly List<T> list;

        public EditOnlyCollection(IEnumerable<T> list)
        {
            this.list = list.ToList();
        }

        public int Count => list.Count;

        public bool IsReadOnly => true;

        public void Add(T item)
        {
            //nop
        }

        public void Clear()
        {
            //nop
        }

        public bool Contains(T item)
        {
            return list.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            list.CopyTo(array, arrayIndex);
        }

        public IEnumerator<T> GetEnumerator()
        {
            return list.GetEnumerator();
        }

        public bool Remove(T item)
        {
            //nop
            return true; //have them think we removed an item
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return list.GetEnumerator();
        }
    }
Nils
  • 59
  • 6