Sử Dụng AutoMapper Trong C# Như Thế Nào?

Sử Dụng AutoMapper Trong C# Như Thế Nào?

AutoMapper trong C# là một object-object mapper. Nó map các thuộc tính giữa hai object khác nhau để chuyển đổi data của object này thành data của object khác.

Nếu chưa hiểu rõ, các ví dụ dưới đây sẽ giải thích cho các bạn khi nào cần sử dụng Automapper:

Cụ thể trong các ví dụ sau

Trong quá trình phát triển ứng dụng các bạn có lẽ cũng đã từng nhiều lần cần chuyển đổi dữ liệu từ object này qua object khác. Bạn có thể hoàn thành công việc này một cách thủ công bằng cách khai báo một object đích rồi chuyển giá trị từ object nguồn qua. Nhưng mà nếu object có nhiều property và nhiều lần cần chuyển đổi thì khá mất thời gian phải không nào?

Trong một trường hợp khác, khi client nhận data từ server bằng domain object để xử lý và hiển thị dữ liệu.Các domain object này tương ứng với các table trong Database, và khi chúng ta thay đổi tên colum trong table thì sẽ phải chỉnh sửa các property trong domain object cùng các xử lý liên quan đến nó.Điều này khiến việc maintain trở nên khó khăn và tốn thời gian hơn.

Để giải quyết vấn đề này, chúng ta cần sử dụng một model khác gọi là Data transfer object, là cái sẽ mapping với domain object. Việc chỉnh sửa domain object sẽ chỉ cần chỉnh sửa mapping với DTO.

 

Hiểu đơn giản Data transfer object (DTO) là một cấu trúc dữ liệu, dùng để truyền dữ liệu từ client đến server và ngược lại.

Như trong ví dụ đầu tiên đề cập thì object đích ở đây chính là đại diện cho DTO. Quá trình chuyển dữ liệu từ object nguồn qua DTO nếu làm thủ công thì mất nhiều thời gian và việc duplicate sẽ xảy ra nếu nhiều lần cần chuyển đổi dữ liệu. Vậy có cách nào chuyển đổi dữ liệu tự động giữa các object với nhau??

AutoMapper là thư viện được tạo ra để giải quyết các vấn đề trên dễ dàng hơn.

Cách sử dụng AutoMapper trong C#

1.  Cài đặt AutoMapper library

Để thực hiện cài đặt automapper ta sử dụng package console hoặc sử dụng nuget packages:

PM> Install-Package AutoMapper

Kết quả sau khi cài đặt thành công hiển thị như dưới

2. Khởi tạo và config AutoMapper

Để thực hiện ví dụ chúng ta có các class Employee, EmployeeDto, Department, DepartmentDto

 public class Employee
    {
        public string Name { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }
    }

    public class EmployeeDto
    {
        public string Name { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }
    }

    public class Department
    {
        public string DeptName { get; set; }
        public string Address { get; set; }
        public string Description { get; set; }

    }

    public class DepartmentDto
    {
        public string DeptName { get; set; }
        public string Address { get; set; }
        public string Description { get; set; }

    }

Chúng ta đang mong muốn chuyển data giữa các object có type là Employee, EmployeeDto và Department, DepartmentDto.

Để thực hiện điều này ta cần thực hiện config để mapping giữa các class theo syntax:

  Và thực hiện như dưới đây:

public class EmployeeConfig
    {
        public static void CreateMap(IMapperConfigurationExpression cfg)
        {
            // config chuyển đổi từ Employee => EmployeeDto
            cfg.CreateMap<Employee, EmployeeDto>();

            // config chuyển đổi từ EmployeeDto => Employee
            cfg.CreateMap<EmployeeDto, Employee>();
        }
    }

    public class DepartmentConfig
    {
        public static void CreateMap(IMapperConfigurationExpression cfg)
        {
            // config chuyển đổi từ Department => DepartmentDto
            cfg.CreateMap<Department, DepartmentDto>();
            // config chuyển đổi từ DepartmentDto => Department
            cfg.CreateMap<DepartmentDto, Department>();
        }
    }

Sau khi config xong, để sử dụng chúng ta cần khởi tạo:

var config = new MapperConfiguration(cfg =>
            {
                EmployeeConfig.CreateMap(cfg);
                DepartmentConfig.CreateMap(cfg);
            });
            var mapper = config.CreateMapper();

Hay đơn giản hơn ta có thể config bằng cách tạo một Profile.Một profile là một class chứa thông tin ánh xạ cho object.Chúng ta tạo một profile chứa tất cả các config của các class cần chuyển đổi.

public class MappingProfile: Profile
    {
        public MappingProfile()
        {
            CreateMap<Employee, EmployeeDto>();
            CreateMap<Department, DepartmentDto>();
        }
    }

Class MappingProfile thừa kế từ class base Profile. Constructor gọi phương thức CreateMap() nó chỉ định kiểu nguồn và đích để ánh xạ với nhau.Trong trường hợp này Employee là nguồn và EmployeeDto là đích.

Sau khi MappingProfile đã sẵn sàng, ta khởi tạo để sử dụng:

            var config = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile(new MappingProfile());
            });
            var mapper = config.CreateMapper();

Để transfer data từ Employee qua EmployeeDto sử dụng syntax:

 var destination = iMapper.Map<Employee , EmployeeDto>(source);

Hay transfer data một List<Employee> qua List<EmployeeDto> và ngược lại như dưới:

static void Main(string[] args)
        {
            //Creating the source object
            var employees = new List<Employee>()
            {
                new Employee
                {
                Name = "James",
                Salary = 20000,
                Address = "London",
                Department = "IT"
                }
                ,
                new Employee
                {
                    Name = "Bon",
                    Salary = 10000,
                    Address = "LA",
                    Department = "IT"
                }
            };
            
            var config = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile(new MappingProfile());
            });
            var mapper = config.CreateMapper();

            // Chuyển đổi danh sách Employee qua danh sách EmployeeDto.
           var empDtos = employees.Select
                            (
                              emp => mapper.Map<Employee, EmployeeDto>(emp)
                            );
            foreach (var empDto in empDtos)
            {
                Console.WriteLine("Name:" + empDto.Name + 
                                  ", Salary:" + empDto.Salary + 
                                  ", Address:" + empDto.Address + 
                                  ", Department:" + empDto.Department);
            }
            Console.ReadLine();
        }

Kết quả nhận được như sau: 

Vậy là quá trình chuyển đổi list<Employee> qua list<EmployeeDto> diễn ra thành công mà không tốn nhiều thời gian phải không nào!

Mọi việc có vẻ như thật đơn giản và quá thuận lợi thì phải?

Trong trường hợp nếu property name của source và property name của destination là không giống nhau thì liệu có thể mapping được không? Các bạn có thể thử nhé!

⇒ Mình đưa kết quả là "Khi property name khác nhau giữa source và destination thi C# Automapper sẽ không map!"

Vậy cách giải quyết trong trường hợp này như thế nào ??

3. Map giữa hai property khi khác name trong Automapper?

Giả sử ta thay đổi property Name sang thành FullName trong class Employee.Khi đó làm sao để map giữa property FullName trong Employee và property Name trong EmployeeDto.

Giải pháp đó là sử dụng ForMember để map giữa các property có name khác nhau.

Ta sẽ cần thay đổi config như dưới:

public class MappingProfile: Profile
    {
        public MappingProfile()
        {
            CreateMap<Employee, EmployeeDto>().ForMember(des=> des.Name, // Property của DTO
                act =>act.MapFrom(src=>src.FullName)); // Map với property của Source
            CreateMap<Department, DepartmentDto>();
        }
    }

Chạy lại ví dụ cho ta kết qua như mong muốn

Tiếp theo ta sẽ tìm cách giải quyết vấn đề trong trường hợp nếu property có kiểu là complex type.

4. AutoMapper Complex Mapping trong C#

public class Employee
    {
        public string FullName { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }
        public Department ObjDepartment { get; set; }
    }

    public class EmployeeDto
    {
        public string Name { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }
        public DepartmentDto ObjDepartment { get; set; }
    }

Chúng ta thấy class Employee  EmployeeDto đều đã được thêm property Objdepartment và có property name đều giống nhau.

Khi map giữa 2 property là kiểu object như trên ta cần phải config để Department map với DepartmentDto.

    public class MappingProfile: Profile
    {
        public MappingProfile()
        {
            CreateMap<Employee, EmployeeDto>().ForMember(des=> des.Name, // Property của DTO
                act =>act.MapFrom(src=>src.FullName)); // Map với property của Source

            CreateMap<Department, DepartmentDto>(); // Department map với DepartmentDto.
        }
    }

Trường hợp 2 property đều có name giống nhau thì chỉ cần config như trên là có thể map được.

 var department = new Department
            {
                DeptName = "D1",
                Address = "Duy tan",
                Description = "Phong IT"
            };

            //Creating the source object
            var employees = new List<Employee>()
            {
                new Employee
                {
                FullName =  "James",
                Salary = 20000,
                Address = "London",
                Department = "IT",
                ObjDepartment = department
                }
                ,
                new Employee
                {
                    FullName = "Bon",
                    Salary = 10000,
                    Address = "LA",
                    Department = "IT",
                    ObjDepartment = department
                }
            };
            var config = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile(new MappingProfile());
            });
            var mapper = config.CreateMapper();

            // Chuyển đổi danh sách Employee qua danh sách EmployeeDto.
           var empDtos = employees.Select
                           (
                             emp => mapper.Map<Employee, EmployeeDto>(emp)
                           );

            foreach (var empDto in empDtos)
            {
                Console.WriteLine("Name:" + empDto.Name + 
                                  ", Salary:" + empDto.Salary +
                                  ", Address:" + empDto.Address + 
                                  ", Department:" + empDto.Department+
                                  ", Description :"
                                         +empDto.ObjDepartment.Description);
            }

            Console.ReadLine();

Kết quá hoàn toàn đúng:

Trường hợp phức tạp hơn đó là property name giữa 2 object là khác nhau như dưới 

 public class Employee
    {
        public string FullName { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }
        public Department ObjDepartment { get; set; }
    }
    public class EmployeeDto
    {
        public string Name { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }
        public DepartmentDto DestinationDepartment { get; set; }
    }
    public class Department
    {
        public string DeptName { get; set; }
        public string Address { get; set; }
        public string Description { get; set; }
    }
    public class DepartmentDto
    {
        public string DeptNameDto { get; set; }
        public string AddressDto { get; set; }
        public string DescriptionDto { get; set; }
    }

Trên hình các property name trong DepartmentDepartmentDto đều khác nhau và property name của Department trong Employee (ObjDepartment) cũng khác name của DeparmentDto trong EmployeeDto (DestinationDepartment).
Để thực hiện map giữa EmployeeEmployeeDto ta cần map DepartmentDepartmentDto như dưới:

 public class MappingProfile: Profile
    {
        public MappingProfile()
        {
            CreateMap<Employee, EmployeeDto>()
                .ForMember(des=> des.Name,
                           act =>act.MapFrom(src=>src.FullName))
                .ForMember(des=>des.DestinationDepartment,
                           act=>act.MapFrom(src=>src.ObjDepartment)); 

            CreateMap<Department, DepartmentDto>()
               .ForMember(des=> des.DeptNameDto,
                                act=>act.MapFrom(src=>src.DeptName))
               .ForMember(des=> des.AddressDto,
                                act=>act.MapFrom(src=>src.Address))
               .ForMember(des=> des.DescriptionDto,
                                act=>act.MapFrom(src=>src.Description));
        }
    }

Sau đó chạy lại chúng ta vẫn có kết quả đúng.

5. Mapping Complex type to Primitive Type using AutoMapper in C#

Có bao giờ các bạn gặp trường hợp này chưa, cần map giữa complex type sang primitive type ?

public class Employee
    {
        public string FullName { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }
        public Department ObjDepartment { get; set; }
    }
    public class EmployeeDto
    {
        public string Name { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }

        // Các property dưới đều nằm trong DepartmentDto
        public string DeptNameDto { get; set; }
        public string AddressDto { get; set; }
        public string DescriptionDto { get; set; }
    }

Như trên ảnh chúng ta cần map giữa complex property ObjDepartment trong Employee với DeptNameDto, AddressDto, DescriptionDto trong EmployeeDto.
Để làm được điều này ta cần map với mỗi property trong ObjDepartment qua DeptNameDto, AddressDto, DescriptionDto.

CreateMap<Employee, EmployeeDto>()
.ForMember(des=> des.Name,act =>act.MapFrom(src=>src.FullName))
.ForMember(des=> des.DeptNameDto,
                 act=>act.MapFrom(src=>src.ObjDepartment.DeptName))
.ForMember(des=> des.AddressDto,
                 act=>act.MapFrom(src=>src.ObjDepartment.Address))
.ForMember(des=> des.DescriptionDto,
                 act=>act.MapFrom(src=>src.ObjDepartment.Description))

Chạy lại chương trình chúng ta vẫn có kết quả đúng như mong đợi.

Ngoài ra trong trường hợp muốn map ngược lại tức là từ primitive type qua complex type như sau :

public class Employee
    {
        public string FullName { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }

        // Các property dưới đều nằm trong Department, 
        // sẽ cần để map với DepartmentDto trong EmployeeDto
        public string DeptName { get; set; }
        public string DeptAddress { get; set; }
        public string Description { get; set; }
    }
    public class EmployeeDto
    {
        public string Name { get; set; }
        public int Salary { get; set; }
        public string Address { get; set; }
        public string Department { get; set; }
        public DepartmentDto DepartmentDto { get; set; }
    }

Ta thực hiện config như dưới để map:

CreateMap<Employee, EmployeeDto>()
.ForMember(des => des.Name,act => act.MapFrom(src =>src.FullName))
 .ForMember(des => des.DepartmentDto, act => act.MapFrom(src => new DepartmentDto
                {
                    DeptNameDto =  src.DeptName,
                    AddressDto = src.DeptAddress,
                    DescriptionDto = src.Description
                }));

Sau khi chạy lại thì kết quả hoàn toàn chính xác:

Source code:

 //Creating the source object
            var employees = new List<Employee>()
            {
                new Employee
                {
                FullName =  "James",
                Salary = 20000,
                Address = "London",
                Department = "IT",
                Description = "aaa",
                DeptName = "bbb",
                DeptAddress = "HN"
                }
                ,
                new Employee
                {
                    FullName = "Bon",
                    Salary = 10000,
                    Address = "LA",
                    Department = "IT",
                    Description = "aaa",
                    DeptName = "bbb",
                    DeptAddress = "HN"
                }
            };
            var config = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile(new MappingProfile());
            });
            var mapper = config.CreateMapper();

            // Chuyển đổi danh sách Employee qua danh sách EmployeeDto.
           var empDtos = employees.Select
                         (
                           emp => mapper.Map<Employee, EmployeeDto>(emp)
                         );

            foreach (var empDto in empDtos)
            {
                Console.WriteLine("Name:" + empDto.Name + 
                                  ", Salary:" + empDto.Salary +
                                  ", Address:" + empDto.Address + 
                                  ", Department:" + empDto.Department+
                                  ", Description :"
                                         +empDto.DepartmentDto.DescriptionDto);
            }

            Console.ReadLine();

Tạm kết

Trên đây mình đã giới thiệu cho các bạn cơ bản hiểu về automapper được dùng trong C# như thế nào. Việc chuyển đổi dữ liệu qua lại giữa các object một cách tự động sẽ khiến chúng ta tiết kiệm thời gian và quá trình maintain source code cũng trở nên dễ dàng hơn. Hi vọng bài viết sẽ mang lại nhiều lợi ích cho dự án của các bạn.

Bài viết khá dài,cảm ơn các bạn đã kiên nhẫn đọc !Mong được sự góp ý của các bạn để mình có động lực đóng góp cho cộng đồng :)