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除外)。

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

      参考资料:

  • 相关阅读:
    bzoj1415 NOI2005聪聪和可可
    Tyvj1952 Easy
    poj2096 Collecting Bugs
    COGS 1489玩纸牌
    COGS1487 麻球繁衍
    cf 261B.Maxim and Restaurant
    cf 223B.Two Strings
    cf 609E.Minimum spanning tree for each edge
    cf 187B.AlgoRace
    cf 760B.Frodo and pillows
  • 原文地址:https://www.cnblogs.com/xishuai/p/3700052.html
Copyright © 2011-2022 走看看