Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Reduced AutoMapper: Auto-Map Objects 80 Percent Faster

DZone's Guide to

Reduced AutoMapper: Auto-Map Objects 80 Percent Faster

Object-object mapping works by transforming an input object of one type into an output object of a different type.

· Performance Zone
Free Resource

Evolve your approach to Application Performance Monitoring by adopting five best practices that are outlined and explored in this e-book, brought to you in partnership with BMC.

From the AutoMapper CodePlex Web Page, we can see that AutoMapper is an object-object mapper. Object-object mapping works by transforming an input object of one type into an output object of a different type. It has a large amount of settings which sometimes are really hard to setup. In my projects, I needed to auto-map simple objects which don’t have collection properties, only a big tree of custom property types- TestCase object which has a property of type TestStep and so on. Also, there are rare cases in which the AutoMapper is not working. So, I created ReducedAutoMapper, which is only 150 lines of code but it runs 80% faster that AutoMapper.

Reduced AutoMapper Explained

The main goal of the object-object mappers is to map object A to object B.

Original Object Type- Not Serializable

public class FirstObject
{
    public FirstObject()
    {
    }

    public string FirstName { get; set; }
    public string SecondName { get; set; }
    public string PoNumber { get; set; }
    public decimal Price { get; set; }
    public DateTime SkipDateTime { get; set; }
    public SecondObject SecondObjectEntity { get; set; }
    public List<SecondObject> SecondObjects { get; set; }
    public List<int> IntCollection { get; set; }
    public int[] IntArr { get; set; }
    public SecondObject[] SecondObjectArr { get; set; }
}

Destination Object- Serializable (identical properties, only serialization attributes added)

[DataContract]
public class MapFirstObject
{
    [DataMember]
    public string FirstName { get; set; }
    [DataMember]
    public string SecondName { get; set; }
    [DataMember]
    public string PoNumber { get; set; }
    [DataMember]
    public decimal Price { get; set; }
    [DataMember]
    public MapSecondObject SecondObjectEntity { get; set; }

    public MapFirstObject()
    {
    }
}

The first step in the object-object mapper is to register the relations between the Original and Destination objects.

private Dictionary<object, object> mappingTypes;
public Dictionary<object, object> MappingTypes
{
    get
    {
        return this.mappingTypes;
    }
    set
    {
        this.mappingTypes = value;
    }
}

public void CreateMap<TSource, TDestination>()
    where TSource : new()
    where TDestination : new()
{
    if (!this.MappingTypes.ContainsKey(typeof(TSource)))
    {
        this.MappingTypes.Add(typeof(TSource), typeof(TDestination));
    }
}

In order to accomplish the task, the class contains mappingTypesDictionary which stores the relations between the original and destination types. Through the generic method CreateMap, the types are added to the dictionary.

Sample Registration

ReducedAutoMapper.Instance.CreateMap<FirstObject, MapFirstObject>();

 How the main AutoMapping Algorithm Works?

In its core, the ReducedAutoMapper heavily uses Reflectionto get the information related to the auto-mapped objects.

public TDestination Map<TSource, TDestination>(
    TSource realObject, 
    TDestination dtoObject = default (TDestination), 
    Dictionary<object, object> alreadyInitializedObjects = null,
    bool shouldMapInnerEntities = true)
    where TSource : class, new()
    where TDestination : class, new()
{
    if (realObject == null)
    {
        return null;
    }
    if (alreadyInitializedObjects == null)
    {
        alreadyInitializedObjects = new Dictionary<object, object>();
    }
    if (dtoObject == null)
    {
        dtoObject = new TDestination();
    }

    var realObjectType = realObject.GetType();
    PropertyInfo[] properties = realObjectType.GetProperties();
    foreach (PropertyInfo currentRealProperty in properties)
    {
        PropertyInfo currentDtoProperty = dtoObject.GetType().GetProperty(currentRealProperty.Name);
        if (currentDtoProperty == null)
        {
            ////Debug.WriteLine("The property {0} was not found in the DTO object in order to be mapped. Because of that we skip to map it.", currentRealProperty.Name);
        }
        else
        {
            if (this.MappingTypes.ContainsKey(currentRealProperty.PropertyType) && shouldMapInnerEntities)
            {
                object mapToObject = this.mappingTypes[currentRealProperty.PropertyType];
                var types = new Type[] { currentRealProperty.PropertyType, (Type)mapToObject };
                MethodInfo method = GetType().GetMethod("Map").MakeGenericMethod(types);
                var realObjectPropertyValue = currentRealProperty.GetValue(realObject, null);
                var objects = new object[]
                {
                    realObjectPropertyValue,
                    null,
                    alreadyInitializedObjects,
                    shouldMapInnerEntities
                };
                if (objects != null && realObjectPropertyValue != null)
                {
                    if (alreadyInitializedObjects.ContainsKey(realObjectPropertyValue) && currentDtoProperty.CanWrite)
                    {
                        // Set the cached version of the same object (optimization)
                        currentDtoProperty.SetValue(dtoObject, alreadyInitializedObjects[realObjectPropertyValue]);
                    }
                    else
                    {
                        // Add the object to cached objects collection.
                        alreadyInitializedObjects.Add(realObjectPropertyValue, null);
                        // Recursively call Map method again to get the new proxy object.
                        var newProxyProperty = method.Invoke(this, objects);
                        if (currentDtoProperty.CanWrite)
                        {
                            currentDtoProperty.SetValue(dtoObject, newProxyProperty);
                        }

                        if (alreadyInitializedObjects.ContainsKey(realObjectPropertyValue) && alreadyInitializedObjects[realObjectPropertyValue] == null)
                        {
                            alreadyInitializedObjects[realObjectPropertyValue] = newProxyProperty;
                        }
                    }
                }
                else if (realObjectPropertyValue == null && currentDtoProperty.CanWrite)
                {
                    // If the original value of the object was null set null to the destination property.
                    currentDtoProperty.SetValue(dtoObject, null);
                }
            }
            else if (!this.MappingTypes.ContainsKey(currentRealProperty.PropertyType))
            {
                // If the property is not custom type just set normally the value.
                if (currentDtoProperty.CanWrite)
                {
                    currentDtoProperty.SetValue(dtoObject, currentRealProperty.GetValue(realObject, null));
                }
            }
        }
    }

    return dtoObject;
}

First it gets the properties of the source object.

var realObjectType = realObject.GetType();
PropertyInfo[] properties = realObjectType.GetProperties();

Next it iterates through them. If a property with the same name it is not present in the destination object, it is skipped. If there is and it is not our custom class (it is a System class like- string, int, DateTime), its value is set to the original’s property one.

else if (!this.MappingTypes.ContainsKey(currentRealProperty.PropertyType))
{
    // If the property is not custom type just set normally the value.
    if (currentDtoProperty.CanWrite)
    {
        currentDtoProperty.SetValue(dtoObject, currentRealProperty.GetValue(realObject, null));
    }
}

If the type of the property is a custom type and it is not present in the  dictionary, it is not auto-mapped.

Otherwise in order the new value of the destination object to be calculated, we use reflection to call recursively the generic Map method.

There is an optimization if the values of the inner property types are already calculated. When a registered destination type is calculated, its value is placed in the alreadyInitializedObjects collection and the method Map is not called recursively afterwards.

If you need to auto-map collection of objects you can use the third method of the ReducedAutoMapper class- MapList.

public List<TDestination> MapList<TSource, TDestination>(List<TSource> realObjects, Dictionary<object, object> alreadyInitializedObjects = null)
    where TSource : class, new()
    where TDestination : class, new()
{
    List<TDestination> mappedEntities = new List<TDestination>();
    foreach (var currentRealObject in realObjects)
    {
        TDestination currentMappedItem = this.Map<TSource, TDestination>(currentRealObject, alreadyInitializedObjects: alreadyInitializedObjects);
        mappedEntities.Add(currentMappedItem);
    }

    return mappedEntities;
}

 Compare AutoMapper with ReducedAutoMapper

I created a simple console application where I initialized a really large objects with more than 1000 properties. The number of the created objects is 100000.

Above you can find the first source class- FirstObject. Below you can find the other two.

SecondObject

public class SecondObject
{
    public SecondObject(string firstNameS, string secondNameS, string poNumberS, decimal priceS)
    {
        this.FirstNameS = firstNameS;
        this.SecondNameS = secondNameS;
        this.PoNumberS = poNumberS;
        this.PriceS = priceS;
        ThirdObject1 = new ThirdObject();
        ThirdObject2 = new ThirdObject();
        ThirdObject3 = new ThirdObject();
        ThirdObject4 = new ThirdObject();
        ThirdObject5 = new ThirdObject();
        ThirdObject6 = new ThirdObject();
    }

    public SecondObject()
    {
    }

    public string FirstNameS { get; set; }
    public string SecondNameS { get; set; }
    public string PoNumberS { get; set; }
    public decimal PriceS { get; set; }
    public ThirdObject ThirdObject1 { get; set; }
    public ThirdObject ThirdObject2 { get; set; }
    public ThirdObject ThirdObject3 { get; set; }
    public ThirdObject ThirdObject4 { get; set; }
    public ThirdObject ThirdObject5 { get; set; }
    public ThirdObject ThirdObject6 { get; set; }
}

ThirdObject

{
    public ThirdObject()
    {
    }

    public DateTime DateTime1 { get; set; }
    public DateTime DateTime2 { get; set; }
    public DateTime DateTime3 { get; set; }
//.. it contains 996 properties more
    public DateTime DateTime1000 { get; set; }
}

The code below tests the ReducedAutoMapper with 100000 objects.

public class Program
{
    static void Main(string[] args)
    {
        DateTime startTime = DateTime.Now;
        List<FirstObject> firstObjects = new List<FirstObject>();
        List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();

        // REDUCED AUTO MAPPER TEST ------------------------------------------------------------
        ReducedAutoMapper.Instance.CreateMap<FirstObject, MapFirstObject>();
        ReducedAutoMapper.Instance.CreateMap<SecondObject, MapSecondObject>();
        ReducedAutoMapper.Instance.CreateMap<ThirdObject, MapThirdObject>();
        for (int i = 0; i < 100000; i++)
        {
            FirstObject firstObject =
                new FirstObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)12.2, DateTime.Now,
                new SecondObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)11.2));
            firstObjects.Add(firstObject);
            System.Console.WriteLine("Object created: {0}", i);
        }
        for (int i = 0; i < firstObjects.Count - 1; i++)
        {
            MapFirstObject mapSecObj = ReducedAutoMapper.Instance.Map<FirstObject, MapFirstObject>(firstObjects[i]);
            mapFirstObjects.Add(mapSecObj);
            System.Console.WriteLine("Map Object: {0}", i);
        }

        DateTime endTime = DateTime.Now;
        System.Console.WriteLine("Finish for {0}", endTime - startTime);
        System.Console.WriteLine();
    }
}

Results

ReducedAutoMapper 100000 Objects Test

The code below tests the AutoMapper with 100000 objects.

public class Program
{
    static void Main(string[] args)
    {
        DateTime startTime = DateTime.Now;
        List<FirstObject> firstObjects = new List<FirstObject>();
        List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();

        // AUTO MAPPER TEST ------------------------------------------------------------
        AutoMapper.Mapper.CreateMap<FirstObject, MapFirstObject>();
        AutoMapper.Mapper.CreateMap<SecondObject, MapSecondObject>();
        AutoMapper.Mapper.CreateMap<ThirdObject, MapThirdObject>();
        for (int i = 0; i < 1000; i++)
        {
            FirstObject firstObject =
                new FirstObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)12.2, DateTime.Now,
                new SecondObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)11.2));
            firstObjects.Add(firstObject);
            System.Console.WriteLine("Object created: {0}", i);
        }
        for (int i = 0; i < firstObjects.Count; i++)
        {
            MapFirstObject mapSecObj = AutoMapper.Mapper.Map<FirstObject, MapFirstObject>(firstObjects[i]);
            mapFirstObjects.Add(mapSecObj);
            System.Console.WriteLine("Map Object: {0}", i);
        }

        DateTime endTime = DateTime.Now;
        System.Console.WriteLine("Finish for {0}", endTime - startTime);
        System.Console.WriteLine();
    }
}

Results

AutoMapper 100000 Objects Test

As you can see from the results above, the ReducedAutoMapper performed 82% better than AutoMapper.

You can download the full source code from my GIT HUB repository – https://github.com/angelovstanton/Projects/tree/master/AAngelov.Utilities/AAngelov.Utilities.Test

Evolve your approach to Application Performance Monitoring by adopting five best practices that are outlined and explored in this e-book, brought to you in partnership with BMC.

Topics:
java ,devops ,theory ,performance ,tools & methods

Published at DZone with permission of Anton Angelov, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}