AutoMapper

AutoMapper là gì?

AutoMapper là một thư viện dùng để mapping object tới object (object-to-object mapper). Nó cho phép bạn mapping các thuộc tính trong cùng một object tới các thuộc tính của một object kiểu khác. Ví dụ, bạn có thể mapping một heavy enity (một entity có nhiều thuộc tính) Customer tới một đối tượng thuộc lớp CustomerDTO hoàn toàn tự động bằng cách sử dụng AutoMapper.

Vấn đề

Đã bao giờ bạn viết đoạn code như này chưa:

Customer customer = GetCustomerFromDB();

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

ShowCustomerInDataGrid(customerViewItem);

Chúng ta có entity Customer, và chúng ta sắp bind các đối tượng Customers lên DataGrid, nhưng như chúng ta đã biết, entity Customer quá nặng, và có quá nhiều thuộc tính không cần thiết, chúng ta cần một object nhẹ cân và duyên dáng hơn, do đó chúng ta sẽ ưu tiên bind object CustomerViewItem lên grid.

Như bạn đã thấy, hiện tại chỉ có bốn dòng code copy các giá trị từ object này tới object khác. Trong khi có thể bạn cần thê hiện thông tin từ 10-15 cột trong grid. Vậy thì sao?

Liệu có phép thần nào giúp chúng ta mapping từ Customer sang CustomerViewItem một cách tự động?

AutoMapper (Giải pháp)

Để bắt đầu, bạn phải cài đặt thư viện AutoMapper vào project của bạn đã. Cách phổ thông mà tôi hay dùng đó là cài qua nuget, bằng câu lệnh:

PM> Install-Package AutoMapper

Tiếp theo, để AutoMapper làm cái công việc thổ tả hộ chúng ta, bạn cần chèn dòng code sau vào đâu đó mà đang cần thực thi.

Mapper.CreateMap<Customer, CustomerViewItem>();

Khi bạn thiết lập xong, bạn cần sử dụng đoạn code sau để lấy về object sau khi map (object đích):

Customer customer = GetCustomerFromDB();

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

ShowCustomerInDataGrid(customerViewItem);

Hãy cùng nhìn lại cả đoạn code để xem chúng ta có gì nãy giờ:

class Program
{
    static void Main(string[] args)
    {
        var program = new Program();
        Mapper.CreateMap<Customer, CustomerViewItem>();
        program.Run();
    }

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

        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
        };
    }
}

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; }
}

Và để chứng minh tất cả các giá trị đã được map, mời bạn xem hình minh họa bên dưới:

Phần này tôi chỉ giới thiệu ở mức cơ bản, các phần sau tôi sẽ có các ví dụ phức tạp hơn và nói về Performance (hiệu năng) khi sử dụng AutoMapper.

 Ví dụ 1:

Cho tới thời điểm này, chúng ta đã biết tất cả công việc mapping đơn giản. Nhưng nếu chúng ta cần cái gì đó phức tạp hơn thì sao? Ví dụ, CustomerViewItem nên có thêm thuộc tính FullName , là kết quả sau khi cộng chuỗi từ hai thuộc tính FirstName và LastName từ class Customer .

Sau khi thôi thêm  public string FullName{get;set;}  vào class CustomerViewItem và chạy ứng dụng trong chế độ Debug, cái tôi nhận được là giá trị null của thuộc tính đó. Cũng đúng thôi, đó là bởi vì AutoMapper rõ ràng không thể thấy thuộc tính FullName nào trong class Customer . “Để mở rộng tầm mắt của nó”, bạn cần phải cho phương thức CreateMap xử lý thêm một chút.

 

Mapper.CreateMap<Customer, CustomerViewItem>()
    .ForMember(cv => cv.FullName, m => m.MapFrom(
    s => s.FirstName + " " + s.LastName))

Ngay lập tức chúng ta có kết quả:

CustomerToViewCustomer

 

Ví dụ 2 (Flattening):

Nếu chúng ta có thuộc tính Company thuộc kiểu Company :

 

public class Customer
{
    public Company Company { get; set; }
    //...
}

public class Company
{
    public string Name { get; set; }
}

và điều mong muốn là có thể map tới thuộc tính CompanyName  của view class:

public class CustomerViewItem
{
    public string CompanyName { get; set; }
    //...
}

Chúng ta phải làm gì để có thể mapping thuộc tính này?

Câu trả lời không cần phải làm gì cả. AutoMapper sẽ tìm đến mọi ngóc ngách trong class của bạn, và nếu nó gặp được cái tên nào đang tìm kiếm, nó sẽ thực hiện mapping giúp bạn.

Ví dụ 3 (Customer Type Resolvers)

Chúng ta phải làm gì nếu bạn có thuộc tính VIP thuộc kiểu boolean trong class Customer của bạn

 

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

và bạn muốn map nó tới thuộc tính VIP thuộc kiểu String , và diễn tả nó bằng ký hiệu “Y” hoặc “N”?

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

Tốt thôi, chúng ta có thể giải quyết vấn đề này theo cách làm với thuộc tính FullName , nhưng có một cách thuận tiện hơn đó là sử dụng cusom resolvers. Vì thế, hãy cùng tạo một cusomer resolver mà nó sẽ giải quyết vấn đề VIP cho chúng ta.

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

Và chỉ thêm 1 dòng code nữa vào quá trình CreateMap :

.ForMember(cv => cv.VIP, m => m.ResolveUsing<VIPResolver>().FromMember(x => x.VIP));

Ví dụ 4 (Custom Formatters):

Chúng ta sẽ phải làm gì nếu tôi muốn AutoMapper sử dụng custom format của kiểu DateTime  thay vì chỉ sử dụng ToString , khi nào nó mapping thuộc tính kiểu DateTime sang thuộc tính thuộc kiểuString ? Ví dụ tôi muốn sử dụng phương thức ToLongDateString để thể hiện ngày sinh theo một cách nhìn khác.

Để làm điều này, chúng ta thêm các dòng code sau:

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

Và phải đảm bảo rằng AutoMapper biết chúng ta sẽ dùng nó ở đâu:

.ForMember(cv => cv.DateOfBirth, m => m.AddFormatter<DateFormatter>());

Đến bây giờ, tôi có:

CustomerToViewCustomer

Thật tuyệt vời! BirthDate đã được thể hiện theo ngôn ngữ tôi thiết lập trên Windows.

Hiệu suất khi sử dụng AutoMapper (Performance)

Sau khi tôi post bài giới thiệu và các ví dụ về AutoMapper, chắc chắn có bạn sẽ có thắc mắc liên quan đến hiệu suất sử dụng AutoMapper. Vì thế tôi quyết định sẽ đo thời gian thực hiện việc mapping bằng AutoMapper, và mapping thủ công.

Đầu tiên, tôi có một đoạn code trả về 100000 đối tượng thuộc lớp Customer , và xuất tất ra đối tượng list customers

Thời gian mapping với AutoMapper

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));

Thời gian mapping thủ công

 

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));

Tôi đã chạy đoạn test trên nhiều lần, và đây là một trong số kết quả nhận được:

AutoMapper: 2117

Manual Mapping: 293

 

 Có vẻ như mapping thủ công nhanh gấp 7 lần mapping tự động. Nhưng khoan đã, bạn hãy xem lại, bạn chỉ mất 2 giây để lấy về hàng ngàn đối tượng customers

Việc lựa chọn sử dụng AutoMapper hoàn toàn phụ thuộc vào hiệu suất có quan trọng với bạn hay không. Tôi không nghĩ rằng bạn sẽ có nhiều trường hợp phải cân nhắc có nên mapping thủ công hay không vì vấn đề liên quan đến hiệu năng. Vì vậy cứ thoải mái đi!

 Nguồn: http://apollo13.vn/lap-trinh/net/automapper-phan-1.html