一文为你详细讲解对象映射库【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中将其称为映射源和映射目标。

关于本节映射都通过如下静态方法来实现,简单粗暴。

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

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

接下来我们再来看若映射源为空,那么是否会进行映射,还是抛异常呢?

        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对象属性全部改为小写:

    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到底有多强,磕不屎你哟。是否支持继承映射哎。

    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其他用途,想必上述场景已经够我们用了吧,如果你觉得不够用,请私信我,我再加上啊。

时间: 2024-10-26 18:59:26

一文为你详细讲解对象映射库【AutoMapper】所支持场景的相关文章

JavaScript事件详细讲解_javascript技巧

事件的概念 事件:指的是文档或者浏览器窗口中发生的一些特定交互瞬间.我们可以通过侦听器(或者处理程序)来预定事件,以便事件发生的时候执行相应的代码. 一.事件流 1.事件流:描述的是在页面中接受事件的顺序 2.事件冒泡:由最具体的元素接收,然后逐级向上传播至最不具体的元素的节点(文档) 3.事件捕获:最不具体的节点先接收事件,而最具体的节点应该最后接收事件 二.事件处理 1.HTML事件处理:直接添加到HTML结构中 2.DOM0级事件处理:把一个函数赋值给一个事件处理程序属性 3.DOM2级事

详细讲解XML数据库中几个容易混淆的概念

xml|概念|数据|数据库     当开发人员谈及XML数据库时,他们往往指的是两个概念:存储XML数据的数据库,利用XML数据库的DBMS.绝大多数主要的DBMS产品允许你无需改变现有的数据库就可以集成XML数据到程序中.现在让我们讨论一下XML数据库并探讨其特性. 原始的XML数据库 一个原始的XML数据库(NXD)可以是很简单,也可以是很复杂.我这样定义一个数据库的概念:永久稳定数据的集合.在这样的定义下,一个NXD能在逻辑上存储一个XML文档.当XML:DBinitiative对一个NX

Java基础:JVM(Java 虚拟机)的详细讲解

可能有很多学习Java的朋友还不知道Java的运行原理.Java虚拟机是怎么工作的,本文将为你详细讲解(JVM)Java 虚拟机. 在Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器.这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口.编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行.在Java中,这种供虚拟机理解的代码叫做字节码(ByteCode),它不面向任何特定的处理器,只面向虚拟机.每一种平台的

Dozer对象映射框架Map到JSONString映射问题排查

引言 Dozer是一个优秀的对象映射的框架,可以帮助程序员减少大量的对象之间映射的get/set代码,在ATA上有好几篇文章介绍了dozer的使用:dozer开发手册使用Dozer帮你提高开发效率(解决繁琐的DO转BO.TO转BO问题) 有兴趣的同学,可以去看下基本的使用. 问题 我在开发后台系统中,经常会遇到从前台提交的对象,转为后台的服务模型对象做操作,通过dozer工具,灵活的配置就可以轻易的解决.我需要将将一个对象的Map对象转为json的字符串,按照dozer的文档需要编写自定义的Co

Java技术_基础技术(0003)_类执行顺序详解+实例(阿里面试题)+详细讲解+流程图

Java技术_基础技术(0001)_后台模拟调用action Java技术_基础技术(0002)_中间件启动class加载顺序(以tomcat为例) Java技术_基础技术(0003)_类执行顺序详解+实例(阿里面试题)+详细讲解+流程图 Java技术_基础技术(0004)_eclipse远程调试tomcat 基础技术: 对于java类各个成员的执行顺序,现拿出阿里的面试题作为样例(例子非常好,是用心出的题),在这里重新说明一下java类执行顺序的原则. 源码下载(包含题.结果.分析过程) 一.

java中Integer包装类的详细讲解(java二进制操作,所有进制转换)

程序员都很懒,你懂的! 今天为大家分享的是Integer这个包装类.在现实开发中,我们往往需要操作Integer,或者各种进制的转换等等.我今天就为大家详细讲解一下Integer的使用吧.看代码: package com.herman.test; public class IntegerTest { public static void main(String[] args) { System.out.println("Integer中的常量***************************

详细讲解Linux系统中动态DNS的配置过程

在大多数家庭网络环境,通过DSL或者Cable Modem连接网络的主机IP地址都是通过DHCP获得并随时间经常改变,这会给DNS解析带来问题.第18章"comfigurition DNS"一节中假定的服务器地址是固定不变的.因此,产生了两大类DNS: "静态DNS"当ISP为你提供固定不变的静态IP地址时,你的DNS服务器作为你的站点认证信息源.你可以把静态DNS看作是DNS的"传统"或"常规"模式. "动态DNS

详细讲解安全升级MySQL的方法_Mysql

MySQL升级是非常必要的. 我们在Percona Support上列出了关于MySQL升级最佳实践的各种问题.这篇文章推荐了一些不同情况下升级MySQL的方法. 为什么MySQL升级是必须的? 原因有很多,比如:为了使用新增的特性,基于性能方面的考量, 修复的bug. 但是在没有充分的测试以前就应用到你的应用中是非常危险的, 因为升级可以能会让你的应用不能正常运作- 也可能引起性能的问题. 此外, 我建议你关注MySQL的发布信息和Percona Server - 看看最近的版本有什么变化.

详细讲解JavaScript中的this绑定_javascript技巧

this 可以说是 javascript 中最耐人寻味的一个特性,就像高中英语里各种时态,比如被动时态,过去时,现在时,过去进行时一样,无论弄错过多少次,下一次依然可能弄错.本文启发于<你不知道的JavaScript上卷>,对 javasript 中的 this 进行一个总结. 学习 this 的第一步就是明白 this 既不是指向函数自身也不指向函数的作用域.this 实际上是在函数被调用时发生的绑定,它指向什么地方完全取决于函数在哪里被调用. 默认绑定 在 javascript 中 ,最常