Introduction
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 that sometimes are hard to setup. In my projects, I needed to auto-map simple objects that don’t have collection properties, only a big tree of custom property types - TestCase
object that has a property of a 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 than AutoMapper.
Reduced AutoMapper Explained
The primary 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));
}
}
To accomplish the task, the class contains mappingTypes Dictionary
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 Reflection to 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)
{
}
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)
{
currentDtoProperty.SetValue(dtoObject, alreadyInitializedObjects
[realObjectPropertyValue]);
}
else
{
alreadyInitializedObjects.Add(realObjectPropertyValue, null);
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)
{
currentDtoProperty.SetValue(dtoObject, null);
}
}
else if (!this.MappingTypes.ContainsKey(currentRealProperty.PropertyType))
{
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 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 (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, for 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 enormous 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; }
public DateTime DateTime1000 { get; set; }
}
The code below tests the ReducedAutoMapper
with 100000
objects.
public class Program
{
static void Main(string[] args)
{
Profile("Test Reduced AutoMapper 10 Runs 10k Objects", 10, () => MapObjectsReduceAutoMapper());
System.Console.ReadLine();
}
static void Profile(string description, int iterations, Action actionToProfile)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++)
{
actionToProfile();
}
watch.Stop();
System.Console.WriteLine(description);
System.Console.WriteLine("Total: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
watch.ElapsedMilliseconds, watch.ElapsedTicks, iterations);
var avgElapsedMillisecondsPerRun = watch.ElapsedMilliseconds / iterations;
var avgElapsedTicksPerRun = watch.ElapsedMilliseconds / iterations;
System.Console.WriteLine("AVG: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
avgElapsedMillisecondsPerRun, avgElapsedTicksPerRun, iterations);
}
static void MapObjectsReduceAutoMapper()
{
List<FirstObject> firstObjects = new List<FirstObject>();
List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();
ReducedAutoMapper.Instance.CreateMap<FirstObject, MapFirstObject>();
ReducedAutoMapper.Instance.CreateMap<SecondObject, MapSecondObject>();
ReducedAutoMapper.Instance.CreateMap<ThirdObject, MapThirdObject>();
for (int i = 0; i < 10000; 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);
}
foreach (var currentObject in firstObjects)
{
MapFirstObject mapSecObj = ReducedAutoMapper.Instance.Map<FirstObject, MapFirstObject>(currentObject);
mapFirstObjects.Add(mapSecObj);
}
}
}
Results
The code below tests the AutoMapper
with 100000
objects.
public class Program
{
static void Main(string[] args)
{
Profile("Test Original AutoMapper 10 Runs 10k Objects", 10, () => MapObjectsAutoMapper());
System.Console.ReadLine();
}
static void Profile(string description, int iterations, Action actionToProfile)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++)
{
actionToProfile();
}
watch.Stop();
System.Console.WriteLine(description);
System.Console.WriteLine("Total: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
watch.ElapsedMilliseconds, watch.ElapsedTicks, iterations);
var avgElapsedMillisecondsPerRun = watch.ElapsedMilliseconds / iterations;
var avgElapsedTicksPerRun = watch.ElapsedMilliseconds / iterations;
System.Console.WriteLine("AVG: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
avgElapsedMillisecondsPerRun, avgElapsedTicksPerRun, iterations);
}
static void MapObjectsAutoMapper()
{
List<FirstObject> firstObjects = new List<FirstObject>();
List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();
AutoMapper.Mapper.CreateMap<FirstObject, MapFirstObject>();
AutoMapper.Mapper.CreateMap<SecondObject, MapSecondObject>();
AutoMapper.Mapper.CreateMap<ThirdObject, MapThirdObject>();
for (int i = 0; i < 10000; 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);
}
foreach (var currentObject in firstObjects)
{
MapFirstObject mapSecObj = AutoMapper.Mapper.Map<FirstObject, MapFirstObject>(currentObject);
mapFirstObjects.Add(mapSecObj);
}
}
}
Results
As you can see from the results above, the ReducedAutoMapper performed >180% better thanAutoMapper.
So Far in the C# Series
1. Implement Copy Paste C# Code
2. MSBuild TCP IP Logger C# Code
3. Windows Registry Read Write C# Code
4. Change .config File at Runtime C# Code
5. Generic Properties Validator C# Code
6. Reduced AutoMapper- Auto-Map Objects 180% Faster
7. 7 New Cool Features in C# 6.0
8. Types Of Code Coverage- Examples In C#
9. MSTest Rerun Failed Tests Through MSTest.exe Wrapper Application
10. Hints For Arranging Usings in Visual Studio Efficiently
11. 19 Must-Know Visual Studio Keyboard Shortcuts – Part 1
12. 19 Must-Know Visual Studio Keyboard Shortcuts – Part 2
13. Specify Assembly References Based On Build Configuration in Visual Studio
14. Top 15 Underutilized Features of .NET
15. Top 15 Underutilized Features of .NET Part 2
16. Neat Tricks for Effortlessly Format Currency in C#
17. Assert DateTime the Right Way MSTest NUnit C# Code
18. Which Works Faster- Null Coalescing Operator or GetValueOrDefault or Conditional Operator
19. Specification-based Test Design Techniques for Enhancing Unit Tests
20. Get Property Names Using Lambda Expressions in C#
21. Top 9 Windows Event Log Tips Using C#
If you enjoy my publications, feel free to SUBSCRIBE
Also, hit these share buttons. Thank you!
Source Code
The post- Reduced AutoMapper- Auto-Map Objects 80% Faster appeared first on Automate The Planet.
All images are purchased from DepositPhotos.com and cannot be downloaded and used for free. License Agreement