Translate

Monday 29 December 2014

Auto Mapper Definition, Examples and their performance And Manual Mapping

Auto Mapper:

AutoMapper is an object-object mapper which allows you to solve issues with mapping the same properties from one object of one type to another object of another type. For example, mapping a heavy entity Customer object to the CustomerDTO could be done with AutoMapper automatically.

The Problem;

Have you ever had to write code like this:

Customer customer = GetCustomerFromDB();

CustomerViewItem customerViewItem = new CustomerViewItem()
                           {
                               FirstName = customer.FirstName,
                               LastName = customer.LastName,
                               DateOfBirth = customer.DateOfBirth,
                               NumberOfOrders = customer.NumberOfOrders
                           };

ShowCustomerInDataGrid(customerViewItem);

A sample scenario could be:
We have our domain model which has aCustomerentity, and we are going to showCustomers in aDataGrid, and for that, we need a much lighter objectCustomerViewItem, a list of which is bound to a grid.

As you see, there are four lines of code which just copy the values from one object to another. It could also be that you will need to show up to 10-15 columns in your grid. What then?
Would you like to have something that will do mapping fromCustomerto theCustomerViewItemautomatically?

Of course, you do, especially if you have another situation like mapping of heavy data objects into DTO objects which are considered to be sent though the wire.
Auto Mapper Simple Example:
class Program
{
static void Main(string[] args)
{
var program = new Program();
program.Run();
}

private void Run()
{
Customer customer = GetCustomerFromDB();

//Auto Map

Mapper.CreateMap<Customer, CustomerViewItem>();

CustomerViewItem customerViewItem =
Mapper.Map<Customer, CustomerViewItem>(customer);

ShowCustomerInDataGrid(customerViewItem);
}

private void ShowCustomerInDataGrid(
CustomerViewItem customerViewItem) { }

private Customer GetCustomerFromDB()
{
return new Customer()
{
DateOfBirth = new DateTime(1987, 11, 2),
FirstName = "Andriy",
LastName = "Buday",
NumberOfOrders = 7,
VIP = 2
};
}
}

public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }

public int NumberOfOrders { get; set; }
}

public class CustomerViewItem
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }

public int NumberOfOrders { get; set; }
}

Custom Map Example 2:

So far, we know all about doing an extremely simple mapping. But, what if we need something more complex, for example, CustomerViewItem should have FullName, which consists of the first and last names of Customer?


After I added 

public string FullName { get; set; } to CustomerViewItem and run my application in Debug mode, I got anullin the property. That is fine, and is because AutoMapper doesn't see anyFullNameproperty in the Customer class. In order to "open its eyes", all you need to do is to change our CreateMap process a bit:

//Custom Map
Mapper.CreateMap<Customer, CustomerViewItem>()
.ForMember(cv => cv.FullName, c => c.MapFrom(s => s.FirstName + " " + s.LastName));






More Complex Example 3 (Custom Type Resolvers)


What if you have a boolean propertyVIPin your Customer class?


public class Customer
{
public bool VIP { get; set; }
}

and want to map it into a stringVIPand represented like "Y" or "N" instead?

public class CustomerViewItem
{
public string VIP { get; set; }
}


Well, we can solve this the same way we did for FullName, but a more appropriate way is to use custom resolvers. So, let's create a customer resolver which will resolve the VIP issue for us.
It looks like:

public class VIPResolver : ValueResolver<bool, string>
{
protected override string ResolveCore(bool source)
{
return source ? "Y" : "N";

}
}

And, only one line is needed for ourCreateMapprocess:

Mapper.CreateMap<Customer, CustomerViewItem>()
.ForMember(cv => cv.VIP, c => c.ResolveUsing<VIPResolver>().FromMember(x => x.VIP));

More Complex Example 4 (Custom Formatters):


What if I want AutoMapper to use my custom formatting of DateTime instead of just usingToString, when it does a mapping from a DateTime to a String property? Let's say, I want to use the ToLongDateString method to show the birth date in a different fashion.
For that, we add:


public class DateFormatter : IValueFormatter
{
public string FormatValue(ResolutionContext context)
{
return ((DateTime)context.SourceValue).ToLongDateString();
}
}


And make sure that AutoMapper knows where to use it:

Mapper.CreateMap<Customer, CustomerViewItem>()
.ForMember(cv => cv.DateOfBirth, c => c.AddFormatter<DateFormatter>());
















Performance Question

To measure execution time of the AutoMapper mapping and manual mapping code.

Measurement of AutoMapper mapping time:


stopwatch.Start();
var autoMapperCVI = new List<customerviewitem>();
foreach (var customer in customers)
{
autoMapperCVI.Add(Mapper.Map<customer,customerviewitem>(customer));
}
stopwatch.Stop();
Console.WriteLine(string.Format("AutoMapper: {0}", stopwatch.ElapsedMilliseconds));

Measurement of the manual mapping time:

stopwatch.Start();
var manualCVI = new List<customerviewitem>();
foreach (var customer in customers)
{
var customerViewItem = new CustomerViewItem()
{
FirstName = customer.FirstName,
LastName = customer.LastName,
FullName = customer.LastName + " " + customer.FirstName,
DateOfBirth = customer.DateOfBirth.ToLongDateString(),
CompanyName = customer.Company.Name,
NumberOfOrders = customer.NumberOfOrders,
VIP = customer.VIP ? "Y" : "N"
};
manualCVI.Add(customerViewItem);
}

stopwatch.Stop();
Console.WriteLine(string.Format("Manual Mapping: {0}", stopwatch.ElapsedMilliseconds));


I ran my tests many times and one of the possible outputs could be:

Result:

AutoMapper: 2117
Manual Mapping: 293

Another Example:


For example, when I tested 1,000 records automapper took 18 milliseconds to 0 for manual. Even with 10,000 records it took automapper 112 milliseconds compared to 1 millisecond.

As I summarized, in most scenarios (1000 records or less) I can live with the slight speed hit to save 10-20 lines of code per mapping