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 a
Customer
entity,
and we are going to showCustomer
s
in aDataGrid
,
and for that, we need a much lighter objectCustomerViewItem
,
a list of which is bound to a grid.
Would
you like to have something that will do mapping from
Customer
to
theCustomerViewItem
automatically?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:
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 anull
in
the property. That is fine, and is because AutoMapper doesn't see
anyFullName
property
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 property
VIP
in
your Customer
class?
public
class
Customer
{
public
bool VIP {
get; set;
}
}
and
want to map it into a string
VIP
and
represented like "Y" or "N" instead?
public
class
CustomerViewItem
{
public
string VIP
{ get; set;
}
}
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)
{
}
}
And, only one line is needed for our
CreateMap
process:
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
For
that, we add: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.
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
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
No comments:
Post a Comment