มาใช้ AutoMapper map Objects กันเถอะ… Action!

AutoMapper มันคืออะไร

AutoMapper ก็คือ library แบบเปิดเผย code ที่สร้างมาเพื่อช่วย programmer map object ที่มีโครงสร้างแบบหนึ่งไปเป็น object ที่มีโครงสร้างอีกแบบหนึ่งได้ง่ายๆโดยการ กำหนด config ในรูปแบบของ convention-based หรือในลักษณะของการใช้รูปแบบชื่อ properties หรือชื่อ method กำหนดเป็น config ให้ AutoMapper map Objects ตามต้องการไว้ที่จุดเดียวได้ครับ

กรณีการใช้ที่เราจะต้องพบเจองานลักษณะนี้บ่อยๆเลย ก็คือ การ cop values จาก objects ที่สร้างจาก layer หนึ่ง ไปให้อีก layer หนึ่งใช้ประโยชน์ เช่น ในระดับ presentation เราต้องการ View Model ที่เหมาะสมเพื่อไว้ใช้นำเสนอข้อมูลให้ผู้ใช้งานได้ง่าย โดยตัวข้อมูลจริงๆจะมากจาก Domain Service ที่จะตอบคืนค่า(response)มาเป็น Data Transfers Object(DTO) หรือจะเป็น Domain Model ก็ได้ แน่นอนว่า model มีโครงสร้างข้อมูลแตกต่างกัน จากทั้งสอง layer ซึ่งงานส่วนนี้ programmer จะต้องเขียน code เพื่อ copy ข้อมูลจาก  DTO ไปเป็น View Model หรือจาก Domain Model ไปเป็น DTO ขั้นตอนนี้ก็คือการ map values จาก source model ไปยัง destination model และงานส่วนนี้ AutoMapper จะมาช่วย programmer เขียน code map ได้ง่ายขึ้นไงครับตามภาพ

* ที่ภาพแก้ไข Service Domain เป็น Domain Service ซึ่งมันก็คือ Service Layer ครับ

จากภาพ เริ่มต้นที่เราจะต้อง config View Model, DTO และ Domain Model ซะก่อนให้ AutoMapper หลังจากนั้น Presentation Layer จะแสดง(render หรือ display) View Model โดยจะเรียกใช้ mapper จาก AutoMapper เพื่อ map DTO ไปเป็น View Model ตามต้องการ แล้วใน layer ของ Domain Service ที่ใช้ logic ของ Domain เมื่อต้องการสร้าง DTO  ก็จะใช้ mapper จาก AutoMapper map Domain Model ไปเป็น DTO ด้วยเช่นกัน

เอาละอธิบายมาพอสมควรแล้วเราไปลองใช้ AutoMapper กันเลยดีกว่า

เริ่มต้น install AutoMapper ง่ายๆโดยใช้ NuGet

ให้ทำเหมือนบทความ มาใช้ Should Assertion Library เขียน UnitTest กันเถอะ… Action! นี้ของผมได้เลยครับ แต่ที่ Packet Manager Console ให้เปลี่ยนเป็น Install-Package AutoMapper

หลังจาก install เรียบร้อยแล้ว ก็เริ่มเขียน code AutoMapper เป็นกรณีการใช้ โดยผมจะนำเสนอแค่ 2 กรณีคือแบบ Flattening กับแบบ Projection นะครับ ตามผมมาเลย

กรณีการ map แบบ Flattening… Action!

แบบ flattenning  ก็คือกรณีที่เรามี model ที่ซับซ้อนอยู่ แล้วเราต้องการ map ไปเป็น model ธรรมดาเรียบๆ แค่ชั้นเดียว เช่น ต้องการ แปลง order จากลูกค้า(customer) ที่ประกอบไปด้วยรายการสินค้า(product)หลายชี้น ให้กลายไปเป็น model เพื่อนำเสนอ order ที่ต้องการสรุป แสดงแค่ ชื่อลูกค้า และยอดรวมมูลค่าการสั่งซื้อเพียงเท่านั้น เมื่อเขียน code จะได้ model ที่ซับซ้อน หน้าตาแบบนี้

//class ใช้เป็นตัวแทน สินค้า

public class Product
{

public decimal Price { get; set; }
public string Name { get; set; }

}

//class ใช้เป็นตัวแทน ลูกค้า

public class Customer
{

public string Name { get; set; }

}

//class ใช้เป็นตัวแทนของรายการสั่งซื้อ

public class OrderLineItem
{

public OrderLineItem(Product product, int quantity)
{

Product = product;
Quantity = quantity;

}

public Product Product { get; private set; }
public int Quantity { get; private set; }

public decimal GetTotal()
{

return Quantity * Product.Price;

}

}

//class ที่ใช้เป็นตัวแทนของ คำสั่งซื้อ

public class Order
{

private readonly IList<OrderLineItem> mOrderLineItems = new List<OrderLineItem>();

public Customer Customer { get; set; }

public OrderLineItem[] GetOrderLineItems()
{

return mOrderLineItems.ToArray();

}

public void AddOrderLineItem(Product product, int quantity)
{

mOrderLineItems.Add(new OrderLineItem(product, quantity));

}

public decimal GetTotal()
{

return mOrderLineItems.Sum(li => li.GetTotal());

}

}

และโครงสร้างที่ผ่านการ map จาก order ที่มีความซับซ้อนมวากมาเป็น model ที่แสดงแค่ ชื่อ และ ผลรวมมูลค่าการสั่งซื้อ ซึ่งมีลักษณะเรียบๆ(flatten) ก็คือ

public class OrderDto
{

public string CustomerName { get; set; }
public decimal Total { get; set; }

}

ถึงตรงนี้ก็พร้อมที่จะเขียน code โดยใช้ AutoMapper กันได้แล้ว โดยผมจะแสดงกรณีการใช้ model ที่มีความซับซ้อนก่อน ก็คือมีลูกค้า มาสั่งซื้อสินค้าแล้ว ตามด้วยการ config ให้ AutoMapper และสุดท้าย จะใช้ AutoMapper ทำการ map Order ไปเป็น OrderDto ครับ ตาม code ข้างล่างนี้

using AutoMapper; //นำ library AutoMapper เข้ามาก่อน

[TestMethod]
public void TestFlattening()
{

//order ที่เป็น model ที่ซับซ้อน จำลองว่ามีลูกค้าชื่อ ChavP มาสั่งซื้อสินค้า 2 รายการ ได้แก่ lay กับ water
var customer = new Customer{Name = “ChavP”};
var order = new Order{Customer = customer};
var lay = new Product{Name = “Lay”,Price = 20m};
var water = new Product{Name = “Water”,Price = 13m};

order.AddOrderLineItem(lay, 1);
order.AddOrderLineItem(water, 1);

//config ให้ AutoMapper โดย Order เป็น source และ OrderDto เป็น destination ตามต้องการ
Mapper.CreateMap<Order, OrderDto>();

//เรียกใช้ AutoMapper ผ่าน static method Map ของ Mapper
OrderDto dto = Mapper.Map<Order, OrderDto>(order);

//ลอง print ผลออกมาดู จะแสดงผลออกมาแบบนี้ ChavP, 33
Console.WriteLine(“{0}, {1}”, dto.CustomerName, dto.Total);

}

จาก class OrderDto นี้จะสังเกตเห็นว่าค่าชอง property Total คือผลรวมของมูลค่าสินค้าที่มีการสั่งซื้อ ที่ได้มาจาก method GetTotal ของ Order โดยตัด Get ออก และ CustomerName ก็มากจากค่าของ Customer.Name ประกอบกันโดยอัตโนมัติ นั่นเอง และนี่ก็คือลักษณะของ convention-based config ของ AutoMapper ยังไงละครับ

กรณีการ map แบบ Projection… Action!

map แบบ projection ก็คือการ map จาก model ที่มีโครงสร้างไม่เป็นตาม convention ก็ได้และมีโครงสร้างไม่เหมือนกับ model ปลายทางเลยก็ได้ด้วย ตัวอย่างนี้ก็ยังเป็น model แบบเรียบชั้นเดียว อยู่นะครับ

//เป็น class ของ เหตุการณ์ที่เกิดขึ้นบนปฏิทิน
public class CalendarEvent
{

public DateTime EventDate { get; set; }
public string Title { get; set; }

}

ต่อไปนี้เป็น class CalendarEventForm ซึ่งมันก็คือ view Model ที่ผมจะนำไปแสดงที่หน้า UI สักแห่งหนึ่ง โดยที่โครงสร้างจะแตกต่างจาก CalendarEvent  เลย

public class CalendarEventForm
{

public DateTime EventDate { get; set; }
public int EventHour { get; set; }
public int EventMinute { get; set; }
public string Title { get; set; }

}

และนี่ก็คือ code ทดสอบกรณีการใช้ AutoMapper ในแบบ Projection ครับ

[TestMethod]
public void TestProjection()
{

// Model
var calendarEvent = new CalendarEvent
{

EventDate = new DateTime(2012, 12, 12, 12, 12, 12),

Title = “Last Day of World”

};

// Configure AutoMapper
Mapper.CreateMap<CalendarEvent, CalendarEventForm>()

.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))
.ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.EventDate.Hour))
.ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.EventDate.Minute));

// Perform mapping
CalendarEventForm form = Mapper.Map<CalendarEvent, CalendarEventForm>(calendarEvent);

//ลอง print ผลออกมาดู จะได้แบบนี้ 12/12/2012 00:00:00, 12, 12, Last Day of World
Console.WriteLine(“{0}, {1}, {2}, {3}”, form.EventDate, form.EventHour, form.EventMinute, form.Title);

}

กรณี map แบบ Projects ก็จบเพียงเท่านี้ครับ คุณจะเห็นว่า AutoMapper มันใช้งานได้ง่าย เขาออกแบบมาได้ตรงไปตรงมาดีครับ ยังมีกรณีการใช้ที่ AutoMapper สนับสนุนอีกหลายรายการเลย เขียนหมดน่าจะยาวเกินไปแล้ว ก็เข้าไปอ่านเองในกรณีการใช้งานที่ต้องการ map จริงเลยดีกว่าที่ AutoMapper หวังว่าบทความนี้เพียงพอที่จะทำให้คุณเห็นประโยชน์จากการใช้ AutoMapper นี้ได้ไม่มากก็น้อยนะครับ…

ขอบคุณครับ

#:P

Advertisements