一文为你详细讲解对象映射库【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其余用途,想必上述场景已经够咱们用了吧,若是你以为不够用,请私信我,我再加上啊。

相关文章
相关标签/搜索