zoukankan      html  css  js  c++  java
  • 【AutoMapper官方文档】DTO与Domin Model相互转换(上)

    写在前面

      AutoMapper目录:

      本篇目录:

      上一篇《【道德经】漫谈实体、对象、DTO及AutoMapper的使用 》,因为内容写的有点跑偏,关于AutoMapper的使用最后只是简单写了下,很明显这种简单的使用方式不能满足项目中复杂的需要,网上找了下AutoMapper相关文档,但差不多都是像我一样简单的概述下,看来懒的不只有我一个,哈哈。在AutoMapper 官方文档中找到其使用的详细说明,天书一样的英文,然后就找相关中文文档,最后还是没找到,这边没办法,只能自己动手,丰衣足食了。英语牛逼的可以直接略过,查看英文文档,本篇也不算是翻译,因为本人英语实在拿不出手,只是按照示例加上自己的一些理解,做个学习笔记,不对的地方还请指正。

      注:虽然上一篇写跑偏了,但是本人真的很喜欢道德经,除了为人处世,在软件设计这方面其实也有体现,也希望可以运用到这上面,如果你和我有一样的想法,请在点击公告栏中的QQ链接,知音难觅啊!

    Flattening-复杂到简单

      Flattening 翻译为压扁、拉平、扁平化的意思,可以理解为使原有复杂的结构变得简化,我们先看下领域模型和DTO代码:

     1     public class Order
     2     {
     3         private readonly IList<OrderLineItem> _orderLineItems = new List<OrderLineItem>();
     4         public Customer Customer { get; set; }
     5         public OrderLineItem[] GetOrderLineItems()
     6         {
     7             return _orderLineItems.ToArray();
     8         }
     9         public void AddOrderLineItem(Product product, int quantity)
    10         {
    11             _orderLineItems.Add(new OrderLineItem(product, quantity));
    12         }
    13         public decimal GetTotal()
    14         {
    15             return _orderLineItems.Sum(li => li.GetTotal());
    16         }
    17     }
    18 
    19     public class Product
    20     {
    21         public decimal Price { get; set; }
    22         public string Name { get; set; }
    23     }
    24 
    25     public class OrderLineItem
    26     {
    27         public OrderLineItem(Product product, int quantity)
    28         {
    29             Product = product;
    30             Quantity = quantity;
    31         }
    32         public Product Product { get; private set; }
    33         public int Quantity { get; private set; }
    34         public decimal GetTotal()
    35         {
    36             return Quantity * Product.Price;
    37         }
    38     }
    39 
    40     public class Customer
    41     {
    42         public string Name { get; set; }
    43     }
    44 
    45     public class OrderDto
    46     {
    47         public string CustomerName { get; set; }
    48         public decimal Total { get; set; }
    49     }

      可以看到领域模型 Order 是很复杂的,但是对于业务场景中的OrderDto却很简单,只有 CustomerName和Total两个属性,AutoMapper配置代码:

     1         public void Example()
     2         {
     3             var customer = new Customer
     4             {
     5                 Name = "George Costanza"
     6             };
     7             var order = new Order
     8             {
     9                 Customer = customer
    10             };
    11             var bosco = new Product
    12             {
    13                 Name = "Bosco",
    14                 Price = 4.99m
    15             };
    16             order.AddOrderLineItem(bosco, 15);
    17             // 配置 AutoMapper
    18             Mapper.CreateMap<Order, OrderDto>();
    19             // 执行 mapping
    20             OrderDto dto = Mapper.Map<Order, OrderDto>(order);
    21             Console.WriteLine("CustomerName:" + dto.CustomerName);
    22             Console.WriteLine("Total:" + dto.Total);
    23         }

      转换效果:

      可以看到配置相当的简单,只要设置下Order和OrderDto之间的类型映射就可以了,我们看OrderDto中的CustomerName和Total属性在领域模型Order中并没有与之相对性,没什么可以转换呢,感觉好神奇的样子,其实仔细发现这些属性的命名都有一定的规则,AutoMapper在做解析的时候会按照PascalCase(帕斯卡命名法),就是一种变量命名法,除了PascalCase还有Hungarian(匈牙利命名法)和camelCase(骆驼命名法),PascalCase就是指混合使用大小写字母来构成变量和函数的名字,首字母要大写,camelCase首字母小写,我们C#命名中,一般使用的是camelCase和PascalCase,比较高级的是PascalCase。

      但是为什么AutoMapper会解析Total呢?因为在领域模型Order中有个GetTotal()方法,AutoMapper会解析“Get”之后的单词,所以会与Total相对应,如果你把OrderDto的属性“Total”改为“Totals”,就会发现得到的“Totals”为0。理解了AutoMapper的解析方式,我们就要注意在编写变量、属性或是方法名称的时候一定要规范,这也是一种好的习惯。

    Projection-简单到复杂

      Projection 翻译为投影,Flattening是由复杂结构简化,Projection正好相反,投影可以理解为由原始结构千变万化,我们看下两种转换结构:

     1     public class CalendarEvent
     2     {
     3         public DateTime EventDate { get; set; }
     4         public string Title { get; set; }
     5     }
     6 
     7     public class CalendarEventForm
     8     {
     9         public DateTime EventDate { get; set; }
    10         public int EventHour { get; set; }
    11         public int EventMinute { get; set; }
    12         public string Title { get; set; }
    13     }

      CalendarEvent是原始结构,CalendarEventForm是我们需要转换后的结构,可以看到CalendarEventForm要比CalendarEvent结构复杂些,看下AutoMapper配置转换代码:

     1         public void Example()
     2         {
     3             var calendarEvent = new CalendarEvent
     4             {
     5                 EventDate = new DateTime(2008, 12, 15, 20, 30, 0),
     6                 Title = "Company Holiday Party"
     7             };
     8 
     9             // 配置 AutoMapper
    10             Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
    11                 .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))//定义映射规则
    12                 .ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.EventDate.Hour))//定义映射规则
    13                 .ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.EventDate.Minute));//定义映射规则
    14 
    15             // 执行 mapping
    16             CalendarEventForm form = Mapper.Map<CalendarEvent, CalendarEventForm>(calendarEvent);
    17 
    18             Console.WriteLine("EventDate:"+form.EventDate);
    19             Console.WriteLine("EventHour:" + form.EventHour);
    20             Console.WriteLine("EventMinute:" + form.EventMinute);
    21             Console.WriteLine("Title:" + form.Title);
    22         }

      和Flattening不同的是,我们除了定义类型映射,还要自定义映射规则,src.EventDate.Date指向dest.EventDate,src.EventDate.Minute指向dest.EventMinute,src.EventDate.Hour指向dest.EventHour,当然我们还可以在MapFrom方法中做一些复杂的映射关系操作,MapFrom接受一个lambda表达式作为参数,可以是任何的Func表达式。Projection适用于由简单到复杂的结构映射,一般体现在业务场景很复杂的情况下。

      【更正:Projection也不一定适用在由简单到复杂的场景,应该说使用Projection就是把AutoMapper的映射配置交给用户来操作】

    Configuration Validation-配置验证

      我们在使用Flattening的前提是我们需要转换的结构命名是没有错误的,但是如果我们没有使用PascalCase命名法,或者说我们命名是错误的,该怎么办呢?比如下面代码:

    1         public class Source
    2         {
    3             public int SomeValue { get; set; }
    4         }
    5 
    6         public class Destination
    7         {
    8             public int SomeValuefff { get; set; }
    9         }

      可以看到Source和Destination中的字段并不相对应,我们测试下AutoMapper映射:

      AssertConfigurationIsValid方法是验证结构映射的,如果配置不正确,会报“AutoMapperConfigurationException”异常错误,如何解决这个问题?你可能会说,就不能改下SomeValuefff的名称吗?这种方法可以,但是如果业务场景中必须要使用怎么办呢,看了上面Projection的映射配置,你可能想到解决方法了,如下:

    1             Mapper.CreateMap<Source, Destination>()
    2                 .ForMember(dest => dest.SomeValuefff, opt => opt.MapFrom(src => src.SomeValue));

      名称不对,我们可以自定义映射规则,虽然这种方式可以,但是如果业务场景中SomeValuefff并不需要,那我们改怎么办?既然有问题,就有解决之道,AutoMapper提供了Ignore方法,忽略不需要映射的数据结构,我们这样配置就可以了:

    1             Mapper.CreateMap<Source, Destination>()
    2                 .ForMember(dest => dest.SomeValuefff, opt => opt.Ignore());

    Lists and Array-集合和数组

      有时候我们除了类型映射之外,还需要对集合类型进行映射,先看个示例:

     1             public void Example()
     2             {
     3                 var sources = new[]
     4                     {
     5                         new Source {Value = 5},
     6                         new Source {Value = 6},
     7                         new Source {Value = 7}
     8                     };
     9                 //配置AutoMapper
    10                 Mapper.Initialize(cfg =>
    11                 {
    12                     cfg.CreateMap<Source, Destination>();
    13                 });
    14                 //配置和执行映射
    15                 IEnumerable<Destination> ienumerableDest = Mapper.Map<Source[], IEnumerable<Destination>>(sources);
    16                 ICollection<Destination> icollectionDest = Mapper.Map<Source[], ICollection<Destination>>(sources);
    17                 IList<Destination> ilistDest = Mapper.Map<Source[], IList<Destination>>(sources);
    18                 List<Destination> listDest = Mapper.Map<Source[], List<Destination>>(sources);
    19 
    20                 Console.WriteLine("ienumerableDest.Count:" + ienumerableDest.Count());
    21                 Console.WriteLine("icollectionDest.Count:" + icollectionDest.Count());
    22                 Console.WriteLine("ilistDest.Count:" + ilistDest.Count());
    23                 Console.WriteLine("listDest.Count:" + listDest.Count());
    24             }

      转换结果:

      Source和Destination结构类型只有一个Value属性,可以看到对集合类型映射也很简单,只需要执行Mapper.Map泛型方法,指定需要转换的集合类型即可,AutoMapper所支持的集合类型包括:

    • IEnumerable
    • IEnumerable<T>
    • ICollection
    • ICollection<T>
    • IList
    • IList<T>
    • List<T>
    • Arrays

      我们在使用Mapper.Map执行类型映射的时候,如果来源类型支持上述集合类型,我们可以把来源类型省略掉,因为AutoMapper会自动判断传入对象sources的类型,如下:

    1                 IEnumerable<Destination> ienumerableDest = Mapper.Map<IEnumerable<Destination>>(sources);
    2                 ICollection<Destination> icollectionDest = Mapper.Map<ICollection<Destination>>(sources);
    3                 IList<Destination> ilistDest = Mapper.Map<IList<Destination>>(sources);
    4                 List<Destination> listDest = Mapper.Map<List<Destination>>(sources);

      还有一种情况是,在使用集合类型类型的时候,类型之间存在继承关系,例如下面我们需要转换的类型:

     1             public class ParentSource
     2             {
     3                 public int Value1 { get; set; }
     4             }
     5             public class ChildSource : ParentSource
     6             {
     7                 public int Value2 { get; set; }
     8             }
     9             public class ParentDestination
    10             {
    11                 public int Value1 { get; set; }
    12             }
    13             public class ChildDestination : ParentDestination
    14             {
    15                 public int Value2 { get; set; }
    16             }

      ChildSource继承ParentSource,ChildDestination继承ParentDestination,看下AutoMapper配置转换代码:

     1             public void Example()
     2             {
     3                 var sources = new[]
     4                     {
     5                         new ParentSource(),
     6                         new ChildSource(),
     7                         new ParentSource()
     8                     };
     9                 //配置AutoMapper
    10                 Mapper.Initialize(cfg =>
    11                 {
    12                     cfg.CreateMap<ParentSource, ParentDestination>()
    13                         .Include<ChildSource, ChildDestination>();
    14                     cfg.CreateMap<ChildSource, ChildDestination>();
    15                 });
    16                 //配置和执行映射
    17                 var destinations = Mapper.Map<ParentSource[], ParentDestination[]>(sources);
    18                 Console.WriteLine("destinations[0] Type:" + destinations[0].GetType().ToString());
    19                 Console.WriteLine("destinations[1] Type:" + destinations[1].GetType().ToString());
    20                 Console.WriteLine("destinations[2] Type:" + destinations[2].GetType().ToString());
    21             }

      转换结果:

      注意在Initialize初始化CreateMap进行类型映射配置的时候有个Include泛型方法,签名为:“Include this configuration in derived types' maps”,大致意思为包含派生类型中配置,ChildSource是ParentSource的派生类,ChildDestination是ParentDestination的派生类,cfg.CreateMap<ParentSource, ParentDestination>().Include<ChildSource, ChildDestination>(); 这段代码只是说明ParentSource和ChildSource之间存在的关系,我们如果把这段代码注释掉,就会报上面“AutoMapperMappingException”类型指定不正确的异常错误,如果我们把下面这段代码:“cfg.CreateMap<ChildSource, ChildDestination>();”注释掉,转换结果为:

      虽然没有报“AutoMapperMappingException”异常,但是可以看出AutoMapper并没有从ChildSource类型映射到ChildDestination类型,而是自动映射到基类型,上面那段映射代码只是说明派生类和基类之间存在的关系,如果派生类需要映射的话,是需要添加派生类的映射的。

    Nested mappings-嵌套映射

      我们上面说的集中映射方式都是简单类型映射,就是类型中并不包含其他类型的映射,如何在嵌套类型中执行映射?请看下面示例:

     1             public class OuterSource
     2             {
     3                 public int Value { get; set; }
     4                 public InnerSource Inner { get; set; }
     5             }
     6             public class InnerSource
     7             {
     8                 public int OtherValue { get; set; }
     9             }
    10             public class OuterDest
    11             {
    12                 public int Value { get; set; }
    13                 public InnerDest Inner { get; set; }
    14             }
    15             public class InnerDest
    16             {
    17                 public int OtherValue { get; set; }
    18             }

      OuterSource和OuterDest类型是我们需要映射的类型,可以看到OuterSource类型中嵌套了InnerSource类型,OuterDest类型中嵌套了InnerDest类型,AutoMapper类型映射配置代码:

     1             public void Example()
     2             {
     3                 var source = new OuterSource
     4                 {
     5                     Value = 5,
     6                     Inner = new InnerSource { OtherValue = 15 }
     7                 };
     8                 //配置AutoMapper
     9                 Mapper.CreateMap<OuterSource, OuterDest>();
    10                 Mapper.CreateMap<InnerSource, InnerDest>();
    11                 //验证类型映射是否正确
    12                 Mapper.AssertConfigurationIsValid();
    13                 //执行映射
    14                 var dest = Mapper.Map<OuterSource, OuterDest>(source);
    15                 Console.WriteLine("dest.Value:" + dest.Value);
    16                 Console.WriteLine("dest.Inner is null:" + (dest.Inner == null ? "true" : "false"));
    17                 Console.WriteLine("dest.Inner.OtherValue:" + dest.Inner.OtherValue);
    18             }

      转换结果:

      上面代码中可以看出,对于嵌套映射,我们不需要配置什么,只要指定下类型映射关系和嵌套类型映射关系就可以了,也就是这段代码:“Mapper.CreateMap<InnerSource, InnerDest>();” 其实我们在验证类型映射的时候加上Mapper.AssertConfigurationIsValid(); 这段代码看是不是抛出“AutoMapperMappingException”异常来判断类型映射是否正确,因为AssertConfigurationIsValid方法没有返回值,只能在catch中捕获了,个人感觉AutoMapper可以提供个bool类型的返回值,验证成功则返回true。

    后记

      示例代码下载:http://pan.baidu.com/s/10A7WM

      贪多嚼不烂,关于AutoMapper的使用先整理这些,后面会陆续更新,还请关注。

      AutoMapper在配置类型映射最注意的一点是,类型中的名称一定要按照PascalCase命名规则(Projection和Ignore除外)。

      如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^

      参考资料:

  • 相关阅读:
    shell管道和重定向
    shell脚本
    Hexo学习笔记--常用命令及部署步骤
    Git学习笔记--Git常用命令
    Spark link集合
    Struts2学习笔记--Struts2的体系结构
    JNI错误记录--JNI程序调用本地库时JVM崩溃
    Hibernate学习笔记--使用ThreadLocal
    Hibernate学习笔记--核心编程
    Hibernate学习笔记--映射配置文件详解
  • 原文地址:https://www.cnblogs.com/xishuai/p/3700052.html
Copyright © 2011-2022 走看看