MapsterMapper / Mapster

A fast, fun and stimulating object to object Mapper
MIT License
4.38k stars 331 forks source link

Mapping behaviour for Dictionary<string, TValue> #369

Closed SvenSchlesinger closed 3 years ago

SvenSchlesinger commented 3 years ago

Hey,

during my last development, I came across an interesting behaviour. I use Mapster to map different classes multiple times. It work's all as expected, as long as those classes do not contain properties of the type Dictionary<string, TValue>.

I use the following basic mapper: _mapper.Map(aggregate, dto, aggregate.GetType(), dto.GetType()). The generic approach with Map<T> can not be used in the applications architecture.

With this, I determined the following behaviour: When I first map two classes, the Dictionary is mapped correctly. Afterwards the dictionary of the source class is edited (pairs added, deleted etc.). The next time I map, I would expect, that the Dictionary in the destination class is simply overwriten by the one of the source class (e.g. same as with the Dictionary<Guid, TValue>, which works great). I`ll give you a simple example:

    public class Class1
    {
        public Dictionary<string, int> Values { get; set; }

        public Class1() { Values = new Dictionary<string, int>(); }
    }

    public class Class2
    {
        public Dictionary<string, int> Values { get; set; }

        public Class2()
        { Values = new Dictionary<string, int>(); }
    }

    [TestClass]
    public class MapperTest
    {
        [TestMethod]
        public void ShoulMapDictWithStringKeys()
        {
            Mapper mapper = new();
            Class1 aggregate = new();
            Class2 dto = new();

            //first time mapping
            aggregate.Values = new Dictionary<string, int>
            {
                { "value1", 1 },
                { "value2", 2 },
                { "value3", 3 }
            };

            _ = mapper.Map(aggregate, dto, aggregate.GetType(), dto.GetType());

            //works as expected
            Assert.AreEqual(3, dto.Values.Count);
            Assert.IsTrue(dto.Values.ContainsKey("value1"));
            Assert.IsTrue(dto.Values.ContainsKey("value2"));
            Assert.IsTrue(dto.Values.ContainsKey("value3"));

            //second time mapping
            aggregate.Values = new Dictionary<string, int>
            {
                { "value4", 4 },
                { "value5", 5 }
            };

            _ = mapper.Map(aggregate, dto, aggregate.GetType(), dto.GetType());

            Assert.AreEqual(2, dto.Values.Count); // count == 5 -> dicts concatenated
            Assert.IsFalse(dto.Values.ContainsKey("value1")); // still contains the key-value pair
        }
    }

After the second mapping, I would expect that the dtos Dictionary does only contain two pairs "value4" : 4 and "value5" : 5. However, it seems like those two pairs are attached to the Dictionary, so that the pairs for value 1, 2 and 3 are still included. Even if I override the Dictionary of my aggregate with an empty Dictionary, the three pairs are still included in the Dictionary after the Mapping.

I guess, that this feature is wanted and can by deactived with some simple configuration, since it works for me e. g. with the Dictionary<Guid, TValue>. Could you please help me, to figure it out? Thanks in advance!

SvenSchlesinger commented 3 years ago

This worked for me as a non generic approach:

Mapster.TypeAdapterConfig.GlobalSettings.ForDestinationType<IDictionary>().BeforeMapping(dest => dest.Clear());

All Dictionaries are cleared before the overwriting.