【AutoMapper官方文档】DTO与Domin Model相互转换(下)

简介:

Mapping Inheritance-映射继承

  关于映射继承,其实在“Lists and Array-集合和数组”这一节点有提到,但是只是说明下AutoMapper解决映射继承所使用的方式,这边我们说下关于AutoMapper在映射继承中的一些特性,比如下面转换示例:

复制代码
1         public class Order { }
2         public class OnlineOrder : Order { }
3         public class MailOrder : Order { }
4 
5         public class OrderDto { }
6         public class OnlineOrderDto : OrderDto { }
7         public class MailOrderDto : OrderDto { }
复制代码

  源对象和目标对象存在继承关系,和“Lists and Array”节点里面的的示例一样,我们首先要配置AutoMapper,添加类型映射关系和依赖关系,如下:

复制代码
1             //配置 AutoMapper
2             Mapper.CreateMap<Order, OrderDto>()
3                   .Include<OnlineOrder, OnlineOrderDto>()
4                   .Include<MailOrder, MailOrderDto>();
5             Mapper.CreateMap<OnlineOrder, OnlineOrderDto>();
6             Mapper.CreateMap<MailOrder, MailOrderDto>();
复制代码

  关于这三段代码的意义,在“Lists and Array”节点中也有说明,如果我们注释掉第一段代码,我们在做派生类映射转换的时候就会报错,如果我们把下面两段代码去掉,我们在做派生类映射转换的时候就会映射到基类,说明第一段代码的意义是,不仅仅包含Order到OrderDto之间的类型映射,还包含Order与OrderDto所有派生类之间的映射,但是只是声明,如果要对派生类之间进行类型映射转换,就还得需要创建派生类之间的类型映射关系。

  我们“Lists and Array”节点中这样执行类型映射转换:

1     var destinations = Mapper.Map<ParentSource[], ParentDestination[]>(sources);

  上面这段转换代码是指定了源泛型类型(Source)和目标泛型类型类型(Dest),所以AutoMapper会根据指定的类型就可以进行转换了,前提是类型映射关系配置正确,要不然就会报“AutoMapperConfigurationException”异常。如果我们不指定目标数据类型,然后就行转换会怎样呢?比如下面转换:

1     var order = new OnlineOrder();
2     var mapped = Mapper.Map(order, order.GetType(), typeof(OrderDto));
3     Console.WriteLine("mapped Type:" + mapped.GetType());

  转换效果:

  代码中我们并没有指定目标数据类型,只是指定一个派生类的基类,如果按照我们的理解,这段代码执行的结果应该是:转换结果mapped对象的类型应该是OrderDto类型,但是确是我们希望想要的OnlineOrderDto类型,虽然我们没有指定目标类型为OnlineOrderDto,但是这一切AutoMapper都帮你做了,就是说AutoMapper会自动判断目标类型与源数据类型存在的关系,并找出最合适的派生类类型。

Queryable Extensions (LINQ)-扩展查询表达式

  注:关于Entity Framework中数据类型映射正在研究,网上找了很多英文文章,还在消化中,这一节点只是简单的说下AutoMapper查询表达式的用法,过几天再整理一篇关于实体框架中运用数据类型映射的文章,功力不够,还请包涵。

  当我们使用Entity Framework与AutoMapper结合进行查询对象转换的时候,使用Mapper.Map方法,就会发现查询结果对象中的所有属性和目标属性对象属性都会转换,当然你也可以在查询结果集中构建一个所需结果的示例,但是这样做并不是可取的,AutoMapper的作者扩展了QueryableExtensions,使得我们在查询的时候就可以实现转换,比如下面示例:

复制代码
 1         public class OrderLine
 2         {
 3             public int Id { get; set; }
 4             public int OrderId { get; set; }
 5             public Item Item { get; set; }
 6             public decimal Quantity { get; set; }
 7         }
 8         public class Item
 9         {
10             public int Id { get; set; }
11             public string Name { get; set; }
12         }
13 
14         public class OrderLineDTO
15         {
16             public int Id { get; set; }
17             public int OrderId { get; set; }
18             public string Item { get; set; }
19             public decimal Quantity { get; set; }
20         }
21 
22         public List<OrderLineDTO> GetLinesForOrder(int orderId)
23         {
24             Mapper.CreateMap<OrderLine, OrderLineDTO>()
25               .ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name));
26 
27             using (var context = new orderEntities())
28             {
29                 return context.OrderLines.Where(ol => ol.OrderId == orderId)
30                          .Project().To<OrderLineDTO>().ToList();
31             }
32         }
复制代码

  代码中的.Project().To就是扩展的查询表达式,详细表达式代码:

  View Code

  我们在前几节点中说的自定义映射规则,其实也是属于查询表达式的一种,结合实体框架可以简单的应用下,比如我们要映射到DTO中一个Count属性,来计算查询结果集中的数量,如下面代码:

1 Mapper.CreateMap<Customer, CustomerDto>()
2     .ForMember(d => d.FullName, opt => opt.MapFrom(c => c.FirstName + " " + c.LastName))
3     .ForMember(d => d.TotalContacts, opt => opt.MapFrom(c => c.Contacts.Count()));

  LINQ支持聚合查询,AutoMapper支持LINQ的扩展方法。在自定义映射中,如果我们讲属性名称TotalContacts改为ContactsCount,AutoMapper将自动匹配到COUNT()扩展方法和LINQ提供程序将转化计数到相关子查询汇总子记录。AutoMapper还可以支持复杂的聚合和嵌套的限制,如果LINQ提供的表达式支持它,例如下面代码:

1 Mapper.CreateMap<Course, CourseModel>()
2     .ForMember(m => m.EnrollmentsStartingWithA,
3           opt => opt.MapFrom(c => c.Enrollments.Where(e => e.Student.LastName.StartsWith("A")).Count()));

  上面计算的是每门课程,学生名字开头为“A”的学生数量。

  不是所有的映射选项都支持表达式,因为它必须有LINQ的支持,支持的有:

  • MapFrom
  • Ignore

  不支持的有:

  • Condition
  • DoNotUseDestinationValue
  • SetMappingOrder
  • UseDestinationValue
  • UseValue
  • ResolveUsing
  • Any calculated property on your domain object

Configuration-配置

Profile-修饰

  AutoMapper提供了个性化设置Profile,使得我们转换后的数据格式可以多变,当然还可以配置全局格式等等,需要继承自Profile,并重写Configure方法,然后在AutoMapper初始化的时候,讲自定义配置添加到映射配置中,如下面示例:

复制代码
 1         public class Order
 2         {
 3             public decimal Amount { get; set; }
 4         }
 5         public class OrderListViewModel
 6         {
 7             public string Amount { get; set; }
 8         }
 9         public class OrderEditViewModel
10         {
11             public string Amount { get; set; }
12         }
13         public class MoneyFormatter : ValueFormatter<decimal>
14         {
15             protected override string FormatValueCore(decimal value)
16             {
17                 return value.ToString("c");
18             }
19         }
20         public class ViewModelProfile : Profile
21         {
22             protected override void Configure()
23             {
24                 CreateMap<Order, OrderListViewModel>();
25                 ForSourceType<decimal>().AddFormatter<MoneyFormatter>();
26             }
27         }
复制代码

  先创建了一个MoneyFormatter字符格式化类,然后创建ViewModelProfile配置类,在Configure方法中,添加类型映射关系,ForSourceType指的是讲元数据类型添加格式化,配置使用代码:

复制代码
 1         public void Example()
 2         {
 3             var order = new Order { Amount = 50m };
 4             //配置 AutoMapper
 5             Mapper.Initialize(cfg =>
 6             {
 7                 cfg.AddProfile<ViewModelProfile>(); 
 8                 cfg.CreateMap<Order, OrderEditViewModel>();
 9             });
10             //执行 mapping
11             var listViewModel = Mapper.Map<Order, OrderListViewModel>(order);
12             var editViewModel = Mapper.Map<Order, OrderEditViewModel>(order);
13 
14             Console.WriteLine("listViewModel.Amount:" + listViewModel.Amount);
15             Console.WriteLine("editViewModel.Amount:" + editViewModel.Amount);
16         }
复制代码

  可以看到在Mapper.Initialize初始化的时候,把ViewModelProfile添加到AutoMapper配置中,泛型类型参数必须是Profile的派生类,因为我们在ViewModelProfile的Configure方法中添加了Order到OrderListViewModel类型映射关系,所以我们再初始化的时候就不需要添加了,转换效果:

Naming Conventions-命名约定

  在“Flattening-复杂到简单”节点中,我们说到AutoMapper映射转换遵循PascalCase(帕斯卡命名规则),所以我们在类型名称命名要按照PascalCase进行命名,除了默认的命名规则,AutoMapper还提供了一种命名规则,如下:

1 Mapper.Initialize(cfg => {
2   cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
3   cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
4 });

  SourceMemberNamingConvention表示源数据类型命名规则,DestinationMemberNamingConvention表示目标数据类型命名规则,LowerUnderscoreNamingConvention和PascalCaseNamingConvention是AutoMapper提供的两个命名规则,前者命名是小写并包含下划线,后者就是帕斯卡命名规则,所以映射转换的效果是:property_name -> PropertyName。

  当然除了在AutoMapper初始化的时候配置命名规则,也可以在Profile中添加全局配置,如下:

复制代码
1 public class OrganizationProfile : Profile 
2 {
3   protected override void Configure() 
4   {
5     SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
6     DestinationMemberNamingConvention = new PascalCaseNamingConvention();
7   }
8 }
复制代码

Conditional Mapping-条件映射

  AutoMapper允许在类型映射之前添加条件,例如下面示例:

复制代码
 1         public class Foo
 2         {
 3             public int baz { get; set; }
 4         }
 5         public class Bar
 6         {
 7             public uint baz { get; set; }
 8         }
 9         public void Example()
10         {
11             var foo = new Foo { baz = 1 };
12             //配置 AutoMapper
13             Mapper.CreateMap<Foo, Bar>()
14                     .ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
15             //执行 mapping
16             var result = Mapper.Map<Foo, Bar>(foo);
17 
18             Console.WriteLine("result.baz:" + result.baz);
19         }
复制代码

  上面示例表示当源数据baz大于0的时候,才能执行映射,关键字是Condition,Condition方法接受一个Func<TSource, bool>类型参数,注意已经指定返回值为bool类型,方法签名:

复制代码
1         //
2         // 摘要:
3         //     Conditionally map this member
4         //
5         // 参数:
6         //   condition:
7         //     Condition to evaluate using the source object
8         void Condition(Func<TSource, bool> condition);
复制代码

  转换效果:

AutoMapper版本变化点

  在AutoMapper1.1版本中,如果我们要对类型嵌套映射中加入自定义类型映射,比如下面示例:

复制代码
1     Mapper.CreateMap<Order, OrderDto>()
2           .Include<OnlineOrder, OnlineOrderDto>()
3           .Include<MailOrder, MailOrderDto>()
4           .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
5     Mapper.CreateMap<OnlineOrder, OnlineOrderDto>()
6           .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
7     Mapper.CreateMap<MailOrder, MailOrderDto>()
8           .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
复制代码

  可以看出,我们需要在每个类型映射的地方要加:.ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));但是Order、OnlineOrder和MailOrder存在继承关系,难道我们如果再加一个派生类映射,就得加一段这样代码,这样就会代码就会变得难以维护。在AutoMapper2.0版本中解决了这一问题,只需要下面这样配置就可以了:

复制代码
1     Mapper.CreateMap<Order, OrderDto>()
2           .Include<OnlineOrder, OnlineOrderDto>()
3           .Include<MailOrder, MailOrderDto>()
4           .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
5     Mapper.CreateMap<OnlineOrder, OnlineOrderDto>();
6     Mapper.CreateMap<MailOrder, MailOrderDto>();
复制代码

类型映射优先级

  1. Explicit Mapping (using .MapFrom())-显式映射:优先级最高,我们使用MapFrom方法定义映射规则,比如:.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))
  2. Inherited Explicit Mapping-继承的显式映射:就是存在继承关系的MapFrom定义映射规则映射。
  3. Ignore Property Mapping-忽略属性映射:使用Ignore方法指定属性忽略映射,比如:Mapper.CreateMap<Source, Destination>().ForMember(dest => dest.SomeValuefff, opt => opt.Ignore());
  4. Convention Mapping (Properties that are matched via convention)-公约映射:公约映射即符合PascalCase命名规则的映射。如Source类中有Value属性,Dest类中也有Value属性,Source和Dest映射关系即是公约映射。
  5. Inherited Ignore Property Mapping-继承的忽略属性映射:优先级最低,就是存在继承关系的忽略属性映射。

  我们举个简单示例来说明下映射优先级:

复制代码
 1         //Domain Objects
 2         public class Order { }
 3         public class OnlineOrder : Order
 4         {
 5             public string Referrer { get; set; }
 6         }
 7         public class MailOrder : Order { }
 8 
 9         //Dtos
10         public class OrderDto
11         {
12             public string Referrer { get; set; }
13         }
14         public void Example2()
15         {
16             //配置 AutoMapper
17             Mapper.CreateMap<Order, OrderDto>()
18                   .Include<OnlineOrder, OrderDto>()
19                   .Include<MailOrder, OrderDto>()
20                   .ForMember(o => o.Referrer, m => m.Ignore());
21             Mapper.CreateMap<OnlineOrder, OrderDto>();
22             Mapper.CreateMap<MailOrder, OrderDto>();
23 
24             //执行 Mapping
25             var order = new OnlineOrder { Referrer = "google" };
26             var mapped = Mapper.Map(order, order.GetType(), typeof(OrderDto));
27         }
复制代码

  转换后mapped对象的Referrer属性值为“google”,但是你发现我们在配置映射规则的时候,不是把Referrer属性给Ignore(忽略)了吗?因为OnlineOrder的ReferrerOrderDto的Referrer属性符合PascalCase命名规则,即是公约映射,虽然忽略属性映射的优先级比公约映射高,但是上面示例中Order和OnlineOrder存在继承关系,即是继承的忽略属性映射,所以优先级比公约映射要低。

后记

  示例代码下载:http://pan.baidu.com/s/1comgI

  


本文转自田园里的蟋蟀博客园博客,原文链接:http://www.cnblogs.com/xishuai/p/3708483.html,如需转载请自行联系原作者

相关文章
|
7月前
|
设计模式 前端开发 Java
DTO和VO的区别及使用场景详解
DTO和VO的区别及使用场景详解
4668 0
|
JSON Java Apache
Bean自动映射工具对比及VO、DTO、PO、DO对象之间的转换
在实际的开发过程中,常常遇到各个层之间对象转换,比如 VO、DTO、PO、DO 等,而如果都是手动set、get,一旦属性较多时,操作起来不仅麻烦,而且浪费时间,因此经常会使用一些工具类,进行对象之间的转换,下面将对象与对象之间转换的方式进行对比,一级对象间的使用进行总结。
Bean自动映射工具对比及VO、DTO、PO、DO对象之间的转换
|
6月前
|
Java
JavaSE——面向对象基础(3/4)-封装、实体JavaBean(什么是封装、封装的设计规范、什么是实体类、实体类的应用场景)
JavaSE——面向对象基础(3/4)-封装、实体JavaBean(什么是封装、封装的设计规范、什么是实体类、实体类的应用场景)
48 6
|
JSON Java fastjson
简单实现_实体类与Json字符串互相转换
简单实现_实体类与Json字符串互相转换
117 1
|
安全 Java Maven
PO VO DTO 转换神器替代BeanUtils 了(二)
PO VO DTO 转换神器替代BeanUtils 了
147 0
|
安全 Java Maven
PO VO DTO 转换神器替代BeanUtils 了(一)
PO VO DTO 转换神器替代BeanUtils 了
200 0
|
Java Spring
PO VO DTO 转换神器替代BeanUtils 了(三)
PO VO DTO 转换神器替代BeanUtils 了
192 0
|
设计模式 前端开发 Java
5年老鸟带你区分 VO、PO、DTO、BO、POJO、JavaBean、Entity(有图,记得收藏)
5年老鸟带你区分 VO、PO、DTO、BO、POJO、JavaBean、Entity(有图,记得收藏)
1606 0
5年老鸟带你区分 VO、PO、DTO、BO、POJO、JavaBean、Entity(有图,记得收藏)
|
数据库
一对多关系domain Model中设置使用AutoMapper时出错
一对多关系domain Model中设置使用AutoMapper时出错
175 0
一对多关系domain Model中设置使用AutoMapper时出错