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.
Join the DZone community and get the full member experience.
Join For Freefrom 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 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)
{
////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
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
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
Published at DZone with permission of Anton Angelov, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments