一文爲你詳細講解對象映射庫【AutoMapper】所支持場景

前言

在AutoMapper未出世前,對象與對象之間的映射,咱們只能經過手動爲每一個屬性一一賦值,時間長了不只是咱們並且老外也以爲映射代碼很無聊啊。這個時候老外的所寫的強大映射庫AutoMapper橫空出世,AutoMapper是一個對象映射庫, 它提供簡單的類型配置,以及簡單的映射測試。對象映射經過將一種類型的輸入對象轉換爲不一樣類型的輸出對象而起做用。項目以前有用過,可是對其瞭解不夠透徹映射時有時候會拋異常,後來棄之,本節咱們來詳細瞭解下AutoMapper映射庫。程序員

AutoMapper基礎版

在AutoMapper中建立映射配置有兩種方式。一種是經過實例化MapperConfiguration類來配置,一種是經過類Mapper中的靜態方法Initialize來配置,下面咱們來看看。數據庫

    public class User
    {
        public int Id { get; set; }
        public int Age { get; set; }
        public string Name { get; set; }
    }

    public class UserDTO
    {
        public int Id { get; set; }
        public int Age { get; set; }
        public string Name { get; set; }
    }
        static void Main(string[] args)
        {
            var user = new User()
            {
                Id = 1,
                Age = 10,
                Name = "Jeffcky"
            };

            var config = new MapperConfiguration(cfg => cfg.CreateMap<User, UserDTO>());
            //或者Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>());

            var mapper = config.CreateMapper();
            //或者var mapper = new Mapper(config);

            //最終調用Map方法進行映射
            var userDTO = mapper.Map<User, UserDTO>(user);
            Console.ReadKey();
        }

在Map映射方法中有兩個參數,咱們通俗講則是從一個映射到另外一個對象,在AutoMapper中將其稱爲映射源和映射目標。app

關於本節映射都經過以下靜態方法來實現,簡單粗暴。測試

            Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>());

            var userDTO = Mapper.Map<User, UserDTO>(user);

接下來咱們再來看若映射源爲空,那麼是否會進行映射,仍是拋異常呢?spa

        static void Main(string[] args)
        {
            User user = null;

            Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>());

            var userDTO = Mapper.Map<User, UserDTO>(user);

            Console.ReadKey();
        }

到此咱們總結出一點:AutoMapper將映射源映射到目標時,AutoMapper將忽略空引用異常。 這是AutoMapper默認設計。設計

是否是到此關於AutoMapper就講完了呢?童鞋想一想全部場景嘛,這個只是最簡單的場景,或者天馬行空想一想其餘問題看看AutoMapper支持不,好比我想一想,AutoMapper對屬性大小寫是否敏感呢?想完就開幹啊。咱們將User對象屬性所有改成小寫:3d

    public class User
    {
        public int id { get; set; }
        public int age { get; set; }
        public string name { get; set; }
    }
        static void Main(string[] args)
        {
            var user = new User()
            {
                id = 1,
                age = 10,
                name = "Jeffcky"
            };

            Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>());

            var userDTO = Mapper.Map<User, UserDTO>(user);

            Console.ReadKey();
        }

到這裏咱們又能夠總結出一點:AutoMapper從映射源到映射目標時不區分大小寫。代理

AutoMapper中級版 

咱們講完基礎版,接下來來進入中級版看看AutoMapper到底有多強,磕不屎你喲。是否支持繼承映射哎。code

    public class Base
    {
        public int Id { get; set; }
        public DateTime CreatedTime { get; set; }
        public DateTime ModifiedTime { get; set; }
    }
    public class User : Base
    {
        public int Age { get; set; }
        public string Name { get; set; }
    }
    public class UserDTO
    {
        public int Id { get; set; }
        public DateTime CreatedTime { get; set; }
        public DateTime ModifiedTime { get; set; }
        public int Age { get; set; }
        public string Name { get; set; }
    }
            var user = new User()
            {
                Id = 1,
                Age = 10,
                Name = "Jeffcky",
                CreatedTime = DateTime.Now,
                ModifiedTime = DateTime.Now
            };

            Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>());

            var userDTO = Mapper.Map<User, UserDTO>(user);

好了,看來也是支持的,咱們總結來一個:AutoMapper從映射源到映射目標支持繼承。講完關於類的繼承,咱們來看看複雜對象,這下AutoMapper想必要有點挑戰了吧。對象

    public class Address
    {
        public string City { get; set; }
        public string State { get; set; }
        public string Country { get; set; }

    }
    public class AuthorModel
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Address Address { get; set; }
    }
    public class AuthorDTO
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
    }
        static void Main(string[] args)
        {
            var author = new AuthorModel()
            {
                Id = 1,
                FirstName = "Wang",
                LastName = "Jeffcky",
                Address = new Address()
                {
                    City = "深圳",
                    State = "1",
                    Country = "中國"
                }
            };

            Mapper.Initialize(cfg => cfg.CreateMap<AuthorModel, AuthorDTO>());

            var authorDTO = Mapper.Map<AuthorModel, AuthorDTO>(author);

            Console.ReadKey();
        }

哇喔,我說AutoMapper還能有這麼智能,那還要咱們程序員幹嗎,在AuthorDTO中咱們將Address扁平化爲簡單屬性,因此此時利用Map再也不是萬能的,咱們須要手動在建立映射配置時經過ForMember方法來自定義指定映射屬性來源,從映射源中的Address複雜對象屬性到AuthorDTO中屬性上。

            var author = new AuthorModel()
            {
                Id = 1,
                FirstName = "Wang",
                LastName = "Jeffcky",
                Address = new Address()
                {
                    City = "深圳",
                    State = "1",
                    Country = "中國"
                }
            };

            Mapper.Initialize(cfg => cfg.CreateMap<AuthorModel, AuthorDTO>()
            .ForMember(d => d.City, o => o.MapFrom(s => s.Address.City))
            .ForMember(d => d.State, o => o.MapFrom(s => s.Address.State))
            .ForMember(d => d.Country, o => o.MapFrom(s => s.Address.Country))
            );
            var authorDTO = Mapper.Map<AuthorModel, AuthorDTO>(author);

如上所給片斷代碼,對於AuthorDTO中的City屬性,咱們指定其值來源於映射源中複雜屬性Address中的City,其他同理,同時對於其餘在相同層次上的屬性不會進行覆蓋。

默認狀況下AutoMapper會將同名且不區分大小寫的屬性進行映射,好比對於有些屬性爲了節省傳輸流量且徹底不須要用到的屬性,咱們壓根不必進行映射,此時AutoMapper中有Ignore方法來忽略映射,以下代碼片斷將忽略對屬性Id的映射。

  Mapper.Initialize(cfg => cfg.CreateMap<AuthorModel, AuthorDTO>()
            .ForMember(d => d.Id, o => o.Ignore())
            );

到此咱們又能夠來一個總結:AutoMapper支持從映射源到映射目標的扁平化。實際上AutoMapper支持扁平化映射,可是前提是遵照AutoMapper映射約定才行,咱們走一個。

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

    }

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

    public class CustomerDTO
    {
        public string CompanyName { get; set; }
    }
        static void Main(string[] args)
        {
            var customer = new Customer()
            {
                Company = new Company()
                {
                    Name = "騰訊"
                }
            };

            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Customer, CustomerDTO>();
            });

            var customerDTO = Mapper.Map<Customer, CustomerDTO>(customer);

            Console.ReadKey();
        }

你看咱們什麼都沒作,結果一樣仍是映射到了目標類中,不過是遵照了AutoMapper的映射約定罷了,看到這個想必你們就立刻明白過來了。若是扁平化映射源類,若想AutoMapper依然可以自動映射,那麼映射目標類中的屬性必須是映射源中複雜屬性名稱加上覆雜屬性中的屬性名稱才行,由於AutoMapper會深度搜索目標類,直到找到匹配的屬性爲止。下面咱們再來看看集合映射。

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public IEnumerable<Order> Orders { get; set; }
    }

    public class Order
    {
        public int Id { get; set; }
        public string TradeNo { get; set; }
        public int TotalFee { get; set; }
    }

    public class CustomerDTO
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public IEnumerable<OrderDTO> OrderDTOs { get; set; }
    }

    public class OrderDTO
    {
        public int Id { get; set; }
        public string TradeNo { get; set; }
        public int TotalFee { get; set; }
    }

上述Customer對象中有Order的集合屬性,因此怕AutoMapper是映射不了,咱們手動配置一下,以下:

        static void Main(string[] args)
        {
            var customer = new Customer()
            {
                Id = 1,
                Name = "Jeffcky",
                Orders = new List<Order>()
                {
                    new Order()
                    {
                        Id =1,
                        TotalFee = 10,
                        TradeNo = "20172021690326"
                    }
                }
            };

            Mapper.Initialize(cfg => cfg.CreateMap<Customer, CustomerDTO>()
            .ForMember(d => d.OrderDTOs, o => o.MapFrom(s => s.Orders))
            );
            var customerDTO = Mapper.Map<Customer, CustomerDTO>(customer);

            Console.ReadKey();
        }

喔,拋出異常了,哈哈,果真AutoMapper還有不支持的,果斷棄之(咱們項目當時就是一直出這樣的問題因而乎棄用了)。慢着,老鐵。利用AutoMapper映射大部分狀況下都會遇到如上異常,因此咱們來分析下,在AutoMapper中,當它偶遇一個接口的目標對象時,它會自動生成動態代理類,怎麼感受好像說到EntityFramework了。 當映射到不存在的映射目標時,這就是內部設計的行爲了。 然而然而,咱們映射目標類卻存在啊,因而乎我修改了AutoMapper映射,將Order到OrderDTO也進行映射配置,而後在配置映射Customer對象再指定Order集合屬性,咱們試試。

            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Order, OrderDTO>();
                cfg.CreateMap<Customer, CustomerDTO>()
               .ForMember(d => d.OrderDTOs, o => o.MapFrom(s => Mapper.Map<IList<Order>, IList<OrderDTO>>(s.Orders)));
            });
            var customerDTO = Mapper.Map<Customer, CustomerDTO>(customer);

老鐵妥妥沒毛病,經過此種方式即便嵌套多層依然也是可以解析,只不過咱們得手動多幾個配置罷了不是,這裏咱們又來一個結論:在映射覆雜對象中的集合屬性時,咱們須要配置集合屬性的映射,而後在複雜對象中再次映射集合屬性

2017-10-13補充

在寫Demo項目時發現還有一種很常見的場景,可是若不注意也會映射出錯,下面咱們來看看。

    public class User
    {
       public string UserName { get; set; }
       public string Email { get; set; }
       public string Password { get; set; }
       public virtual UserProfile UserProfile { get; set; }
    }

    public class UserProfile
    {
      public string FirstName { get; set; }
      public string LastName { get; set; }
      public string Address { get; set; }      
      public virtual User User { get; set; }
    }
    public class UserDTO
    {
        public Int64 ID { get; set; }
        [Display(Name ="First Name")]
        public string FirstName { get; set; }
        [Display(Name="Last Name")]
        public string LastName { get; set; }
        public string Address { get; set; }
        [Display(Name="User Name")]
        public string UserName { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        [Display(Name ="Added Date")]
        public DateTime AddedDate { get; set; }
    }

一樣是扁平化,接下來咱們再來進行映射

            CreateMap<User, UserDTO>()
                .ForMember(d => d.FirstName, m => m.MapFrom(f => f.UserProfile.FirstName))
                .ForMember(d => d.LastName, m => m.MapFrom(f => f.UserProfile.LastName))
                .ForMember(d => d.Address, m => m.MapFrom(f => f.UserProfile.Address));

            CreateMap<UserDTO, User>()
                .ForMember(d => d.UserProfile.FirstName, m => m.MapFrom(f => f.FirstName))
                .ForMember(d => d.UserProfile.LastName, m => m.MapFrom(f => f.LastName))
                .ForMember(d => d.UserProfile.Address, m => m.MapFrom(f => f.Address));            

此時咱們固然能夠利用AfterMap來實現,可是仍是有其餘解決方案,以下:

            CreateMap<UserDTO, User>()
                .ForMember(d => d.UserProfile, m => m.MapFrom(f => f));

            CreateMap<UserDTO, UserProfile>()
                .ForMember(d => d.FirstName, m => m.MapFrom(f => f.FirstName))
                .ForMember(d => d.LastName, m => m.MapFrom(f => f.LastName))
                .ForMember(d => d.Address, m => m.MapFrom(f => f.Address));     

AutoMapper高級版

AutoMapper太強大了,我給跪了,強大到這篇幅不夠,得手動下拉滾動條繼續磕。廢話少說,咱們再來看看AutoMapper使用高級版,自定義值解析,動態對象映射、類型轉換等。

自定義值解析

AutoMapper支持自定義解析,只不過咱們須要實現IValueResolver接口才行,下面咱們來看看。

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

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

實現IValueResolver接口,對映射源加以判斷返回映射目標中的字符串。

    public class VIPResolver : IValueResolver<Customer, CustomerDTO, string>
    {
        public string Resolve(Customer source, CustomerDTO destination, string destMember, ResolutionContext context)
        {
            return source.VIP ? "Y" : "N";
        }
    }

而後在映射配置時使用ResolveUsing來實現上述自定義解析,使用方式有以下兩種。

            var customer = new Customer()
            {
                VIP = true
            };

            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Customer, CustomerDTO>()
                .ForMember(cv => cv.VIP, m => m.ResolveUsing<VIPResolver>());
            });

            //或者
            //Mapper.Initialize(cfg =>
            //{
            //    cfg.CreateMap<Customer, CustomerDTO>()
            //    .ForMember(cv => cv.VIP, m => m.ResolveUsing(new VIPResolver()));
            //});
            var customerDTO = Mapper.Map<Customer, CustomerDTO>(customer);

動態對象映射 

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
            dynamic customer = new ExpandoObject();
            customer.Id = 5;
            customer.Name = "Jeffcky";

            Mapper.Initialize(cfg => { });

            var result = Mapper.Map<Customer>(customer);

            dynamic foo2 = Mapper.Map<ExpandoObject>(result);

類型轉換 

關於上述自定義值解析,咱們一樣能夠用類型轉換類實現,在AutoMapper中存在ConvertUsing方法,該方法相似於C#中的投影同樣,以下:

            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Customer, CustomerDTO>()
                .ConvertUsing(s => new CustomerDTO()
                {
                    VIP = s.VIP ? "Y" : "N"
                });
            });

或者

    public class CustomTypeConverter : ITypeConverter<Customer, CustomerDTO>
    {
        public CustomerDTO Convert(Customer source, CustomerDTO destination, ResolutionContext context)
        {
            return new CustomerDTO
            {
                VIP = source.VIP ? "Y" : "N",
            };
        }
    }
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Customer, CustomerDTO>()
                .ConvertUsing(new CustomTypeConverter());
            });

AutoMapper太強大了,上述已經給出大部分咱們基本上會用到的場景,AutoMapper還支持依賴注入,同時最爽的是有了AutoMapper.QueryableExtensions擴展方法,這針對使用EF的童鞋簡直是福音啊。 經過ProjectTo方法便可映射從數據庫查詢出的IQueryable類型數據。

            IQueryable<Customer> customers = null;

            var customersDTO = customers.ProjectTo<CustomerDTO>();

總結

AutoMapper強大到給跪了,目前該項目已被.NET基金會所支持,看過的,路過的,沒用過的,趕忙走起用起來啊,有時間還會更新AutoMapper其餘用途,想必上述場景已經夠咱們用了吧,若是你以爲不夠用,請私信我,我再加上啊。

相關文章
相關標籤/搜索