DTO与Domin Model相互转换(上)

简介:

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://www.cnblogs.com/xishuai/p/3700052.html,如需转载请自行联系原作者

相关文章
|
人工智能 监控 算法
【AI 现况分析】AI 应用导致的隐私问题分析
【1月更文挑战第27天】【AI 现况分析】AI 应用导致的隐私问题分析
|
算法 API C++
【Qt UI】QT 窗口/控件置顶方法详解
【Qt UI】QT 窗口/控件置顶方法详解
1069 0
|
安全 网络安全 开发者
网站跳转到反诈中心该怎么处理解封恢复正常访问
作为一个网站开发者,我曾经经历了这样的情况:我建设的公司网站被标识为恶意网站,被拦截了。通过调查,我发现这是因为反诈中心下发了拦截令。这种拦截方法为网站域名拦截,即由最高部门下发到各地防诈中心和运营商进行拦截。如果用户打开这样的网站,将会出现解析错误,无法访问。总的来说,网站域名拦截是一种阻断诈骗网站的有效手段,但是在实际操作中也需要更加严格的审核,以防止出现误判的情况。我认为,反诈工作是需要不断提高的,同时也需要更加完善的机制和法律支持。
7733 0
网站跳转到反诈中心该怎么处理解封恢复正常访问
|
SQL 关系型数据库 MySQL
阿里云MySQL数据库价格、购买、创建账号密码和连接数据库教程
阿里云数据库使用指南:购买MySQL、SQL Server等RDS实例,选择配置和地区,完成支付。创建数据库和账号,设置权限。通过DMS登录数据库,使用账号密码访问。同地域VPC内的ECS需将IP加入白名单以实现内网连接。参考链接提供详细步骤。
1432 3
|
8月前
|
供应链 API 开发者
解锁电商数据的无限可能:探秘京东商品SKU信息API接口
京东商品SKU信息API接口是电商开发与运营中的重要工具,帮助开发者获取商品的详细属性,如库存、价格、规格等。通过该接口,电商平台可以丰富商品展示页面,提升用户体验;商家能实时掌握库存动态,优化销售策略;数据分析人员可深入洞察市场趋势,实现精准营销。使用前需注册京东开放平台账号、创建应用并获取API权限,同时仔细阅读API文档以确保正确调用。代码示例展示了如何用Python调用该接口,并处理返回数据。未来,该接口将在个性化推荐、智能库存管理和数据分析等领域发挥更大作用,助力电商业务创新与发展。
433 14
|
8月前
|
关系型数据库 分布式数据库 数据库
瑶池数据库大讲堂|PolarDB HTAP:为在线业务插上实时分析的翅膀
瑶池数据库大讲堂介绍PolarDB HTAP,为在线业务提供实时分析能力。内容涵盖MySQL在线业务的分析需求与现有解决方案、PolarDB HTAP架构优化、针对分析型负载的优化(如向量化执行、多核并行处理)及近期性能改进和用户体验提升。通过这些优化,PolarDB HTAP实现了高效的数据处理和查询加速,帮助用户更好地应对复杂业务场景。
275 4
|
12月前
|
机器学习/深度学习 人工智能 搜索推荐
探究人工智能在医疗健康中的应用与挑战
本文深入探讨了人工智能(AI)在医疗健康领域中的应用及其所面临的挑战。随着科技的不断进步,AI技术在医疗领域的应用日益广泛,从疾病诊断、治疗方案制定到患者护理等方面都展现出巨大的潜力。然而,尽管AI在医疗健康领域取得了显著成果,但也面临着数据隐私、算法偏见和伦理道德等方面的挑战。本文将详细介绍这些应用和挑战,并探讨可能的解决方案,以期为读者提供对AI在医疗健康领域发展的全面理解。
|
8月前
|
存储 前端开发 Java
SpringBoot整合Flowable【05】- 使用流程变量传递业务数据
本文介绍了如何使用Flowable的流程变量来管理绩效流程中的自定义数据。首先回顾了之前的简单绩效流程,指出现有流程缺乏分数输入和保存步骤。接着详细解释了流程变量的定义、分类(运行时变量和历史变量)及类型。通过具体代码示例展示了如何在绩效流程中插入全局和局部流程变量,实现各节点打分并维护分数的功能。最后总结了流程变量的使用场景及其在实际业务中的灵活性,并承诺将持续更新Flowable系列文章,帮助读者更好地理解和应用Flowable。 简要来说,本文通过实例讲解了如何利用Flowable的流程变量功能优化绩效评估流程,确保每个环节都能记录和更新分数,同时提供了全局和局部变量的对比和使用方法。
679 0
SpringBoot整合Flowable【05】- 使用流程变量传递业务数据
单模、多模能混合使用吗?这篇给您讲明白!
单模、多模能混合使用吗?这篇给您讲明白!
291 5
|
NoSQL Java Redis
Redis字符串数据类型之INCR命令,通常用于统计网站访问量,文章访问量,实现分布式锁
这篇文章详细解释了Redis的INCR命令,它用于将键的值增加1,通常用于统计网站访问量、文章访问量,以及实现分布式锁,同时提供了Java代码示例和分布式锁的实现思路。
425 0