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

时间:2023-02-21 14:10:56

写在前面

  AutoMapper目录:

  本篇目录:

  关于AutoMapper写到这基本的东西都差不多了,上一篇定义为灵活配置篇,本篇可以定义为扩展应用篇,加一些补充,关于AutoMapper的项目应用,网上找了几篇英文文章,虽然看不懂,但是代码是相通的,感觉很不错,主要是EntityFramework中运用AutoMapper,数据访问中使用AutoMapper,有支持的,也有反对的,也有提出建议的,自己也正在摸索,希望有机会写篇文章和大家分享下。

  插一句:写这些东西,看的人真的很少,还不如像前几天大家写篇水文,来讨论下C#的好坏增加点人气,呵呵,但是如果是这种思想来编程真是不可饶恕,写这种文章的目的不一定是分享给别人,也是对自己学习的另一种修炼,毕竟肚子没有什么东西,是写不出来,也是在逼迫自己去学习,当去学习一点东西后,发现其实并不像想象的那么简单,还有很多的东西要去学习,恨只恨自己晚生了几年,还需努力。

Mapping Inheritance-映射继承

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

         public class Order { }
public class OnlineOrder : Order { }
public class MailOrder : Order { } public class OrderDto { }
public class OnlineOrderDto : OrderDto { }
public class MailOrderDto : OrderDto { }

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

             //配置 AutoMapper
Mapper.CreateMap<Order, OrderDto>()
.Include<OnlineOrder, OnlineOrderDto>()
.Include<MailOrder, MailOrderDto>();
Mapper.CreateMap<OnlineOrder, OnlineOrderDto>();
Mapper.CreateMap<MailOrder, MailOrderDto>();

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

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

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

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

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

  转换效果:

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

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

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

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

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

         public class OrderLine
{
public int Id { get; set; }
public int OrderId { get; set; }
public Item Item { get; set; }
public decimal Quantity { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
} public class OrderLineDTO
{
public int Id { get; set; }
public int OrderId { get; set; }
public string Item { get; set; }
public decimal Quantity { get; set; }
} public List<OrderLineDTO> GetLinesForOrder(int orderId)
{
Mapper.CreateMap<OrderLine, OrderLineDTO>()
.ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name)); using (var context = new orderEntities())
{
return context.OrderLines.Where(ol => ol.OrderId == orderId)
.Project().To<OrderLineDTO>().ToList();
}
}

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

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions; namespace DTO_AutoMapper使用详解
{
public static class QueryableExtensions
{
public static ProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
{
return new ProjectionExpression<TSource>(source);
}
} public class ProjectionExpression<TSource>
{
private static readonly Dictionary<string, Expression> ExpressionCache = new Dictionary<string, Expression>(); private readonly IQueryable<TSource> _source; public ProjectionExpression(IQueryable<TSource> source)
{
_source = source;
} public IQueryable<TDest> To<TDest>()
{
var queryExpression = GetCachedExpression<TDest>() ?? BuildExpression<TDest>(); return _source.Select(queryExpression);
} private static Expression<Func<TSource, TDest>> GetCachedExpression<TDest>()
{
var key = GetCacheKey<TDest>(); return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression<Func<TSource, TDest>> : null;
} private static Expression<Func<TSource, TDest>> BuildExpression<TDest>()
{
var sourceProperties = typeof(TSource).GetProperties();
var destinationProperties = typeof(TDest).GetProperties().Where(dest => dest.CanWrite);
var parameterExpression = Expression.Parameter(typeof(TSource), "src"); var bindings = destinationProperties
.Select(destinationProperty => BuildBinding(parameterExpression, destinationProperty, sourceProperties))
.Where(binding => binding != null); var expression = Expression.Lambda<Func<TSource, TDest>>(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression); var key = GetCacheKey<TDest>(); ExpressionCache.Add(key, expression); return expression;
} private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerable<PropertyInfo> sourceProperties)
{
var sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == destinationProperty.Name); if (sourceProperty != null)
{
return Expression.Bind(destinationProperty, Expression.Property(parameterExpression, sourceProperty));
} var propertyNames = SplitCamelCase(destinationProperty.Name); if (propertyNames.Length == )
{
sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == propertyNames[]); if (sourceProperty != null)
{
var sourceChildProperty = sourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames[]); if (sourceChildProperty != null)
{
return Expression.Bind(destinationProperty, Expression.Property(Expression.Property(parameterExpression, sourceProperty), sourceChildProperty));
}
}
} return null;
} private static string GetCacheKey<TDest>()
{
return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
} private static string[] SplitCamelCase(string input)
{
return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim().Split(' ');
}
}
}

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

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

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

 Mapper.CreateMap<Course, CourseModel>()
.ForMember(m => m.EnrollmentsStartingWithA,
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初始化的时候,讲自定义配置添加到映射配置中,如下面示例:

         public class Order
{
public decimal Amount { get; set; }
}
public class OrderListViewModel
{
public string Amount { get; set; }
}
public class OrderEditViewModel
{
public string Amount { get; set; }
}
public class MoneyFormatter : ValueFormatter<decimal>
{
protected override string FormatValueCore(decimal value)
{
return value.ToString("c");
}
}
public class ViewModelProfile : Profile
{
protected override void Configure()
{
CreateMap<Order, OrderListViewModel>();
ForSourceType<decimal>().AddFormatter<MoneyFormatter>();
}
}

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

         public void Example()
{
var order = new Order { Amount = 50m };
//配置 AutoMapper
Mapper.Initialize(cfg =>
{
cfg.AddProfile<ViewModelProfile>();
cfg.CreateMap<Order, OrderEditViewModel>();
});
//执行 mapping
var listViewModel = Mapper.Map<Order, OrderListViewModel>(order);
var editViewModel = Mapper.Map<Order, OrderEditViewModel>(order); Console.WriteLine("listViewModel.Amount:" + listViewModel.Amount);
Console.WriteLine("editViewModel.Amount:" + editViewModel.Amount);
}

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

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

Naming Conventions-命名约定

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

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

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

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

 public class OrganizationProfile : Profile
{
protected override void Configure()
{
SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
DestinationMemberNamingConvention = new PascalCaseNamingConvention();
}
}

Conditional Mapping-条件映射

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

         public class Foo
{
public int baz { get; set; }
}
public class Bar
{
public uint baz { get; set; }
}
public void Example()
{
var foo = new Foo { baz = };
//配置 AutoMapper
Mapper.CreateMap<Foo, Bar>()
.ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= )));
//执行 mapping
var result = Mapper.Map<Foo, Bar>(foo); Console.WriteLine("result.baz:" + result.baz);
}

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

         //
// 摘要:
// Conditionally map this member
//
// 参数:
// condition:
// Condition to evaluate using the source object
void Condition(Func<TSource, bool> condition);

  转换效果:

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

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-继承的忽略属性映射:优先级最低,就是存在继承关系的忽略属性映射。

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

         //Domain Objects
public class Order { }
public class OnlineOrder : Order
{
public string Referrer { get; set; }
}
public class MailOrder : Order { } //Dtos
public class OrderDto
{
public string Referrer { get; set; }
}
public void Example2()
{
//配置 AutoMapper
Mapper.CreateMap<Order, OrderDto>()
.Include<OnlineOrder, OrderDto>()
.Include<MailOrder, OrderDto>()
.ForMember(o => o.Referrer, m => m.Ignore());
Mapper.CreateMap<OnlineOrder, OrderDto>();
Mapper.CreateMap<MailOrder, OrderDto>(); //执行 Mapping
var order = new OnlineOrder { Referrer = "google" };
var mapped = Mapper.Map(order, order.GetType(), typeof(OrderDto));
}

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

后记

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

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

  参考资料:

【AutoMapper官方文档】DTO与Domin Model相互转换(下)的更多相关文章

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

    写在前面 AutoMapper目录: [AutoMapper官方文档]DTO与Domin Model相互转换(上) [AutoMapper官方文档]DTO与Domin Model相互转换(中) [Au ...

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

    写在前面 AutoMapper目录: [AutoMapper官方文档]DTO与Domin Model相互转换(上) [AutoMapper官方文档]DTO与Domin Model相互转换(中) [Au ...

  3. Spring Cloud官方文档中文版-Spring Cloud Config(下)-客户端等

    官方文档地址为:http://cloud.spring.io/spring-cloud-static/Dalston.SR2/#_serving_alternative_formats 文中例子我做了 ...

  4. Spring官方文档下载

    Spring框架是目前最流行的java web开发框架,很多时候,我们需要去查看spring的官方文档,这里就简单介绍下如何下载其官方文档. 1.搜索到spring 官网并进入 2.点击DOCS 3. ...

  5. MySQL8&period;0&period;28安装教程全程参考MySQL官方文档

    前言 为了MySQL8.0.28安装教程我竟然在MySQL官方文档逛了一天,至此献给想入门MySQL8.0的初学者.以目前最新版本的MySQL8.0.28为示例进行安装与初步使用的详细讲解,面向初学者 ...

  6. Akka Typed 官方文档之随手记

    ️ 引言 近两年,一直在折腾用FP与OO共存的编程语言Scala,采取以函数式编程为主的方式,结合TDD和BDD的手段,采用Domain Driven Design的方法学,去构造DDDD应用(Dom ...

  7. Kotlin开发语言文档(官方文档)-- 目录

    开始阅读Kotlin官方文档.先上文档目录.有些内容还未阅读,有些目录标目翻译还需琢磨琢磨.后续再将具体内容的链接逐步加上. 文档链接:https://kotlinlang.org/docs/kotl ...

  8. Spring 4 官方文档学习(十二)View技术

    关键词:view technology.template.template engine.markup.内容较多,按需查用即可. 介绍 Thymeleaf Groovy Markup Template ...

  9. Spring 4 官方文档学习(十一)Web MVC 框架之HTTP caching support

    一个良好的HTTP缓存策略可以显著地增进web应用的性能和其客户端的体验.主要使用"Cache-Control" HTTP response header来完成,配合conditi ...

随机推荐

  1. sql常用语句(1)

    --排序 select Row_Number() over(order by a.UserName) as Num --区分性别 then '男' else '女' end SexName Sqlse ...

  2. Oracle函数-DECODE

    DECODE翻译为解码或译码,因为它可以把查询结果翻译成令用户容易理解的内容. 语法: expr: 表达式 search:搜索值 result:结果值 default:默认值 DECODE函数的第一个 ...

  3. VS2013菜单栏文字全大写的问题

    从VS2010转到VS2013,2013新增的很多功能确实很方便,只是有一点,菜单栏文字变成全大写了,看着有点不习惯. 打开注册表编辑器,找到 HKEY_CURRENT_USER\Software\M ...

  4. LaTeX常用数学符号

    之前在写博客做笔记时经常会在Word或WPS里写好数学公式再截图上传,一直觉得这样很low.现在实在是不想再去截图上传了,于是决定开始学一下LaTeX.在博客园中使用数学公式的设置可以参考在博客园使用 ...

  5. Connection 连接字符串释义

    本文将详细介绍如何使用Connection对象连接数据库.对于不同的.NET数据提供者,ADO.NET采用不同的Connection对象连接数据库.这些Connection对象为我们屏蔽了具体的实现细 ...

  6. 切换view的动画

    代码: #import "MainViewController.h" @interface MainViewController () @end @implementation M ...

  7. 7&period;1&period;1&period;关闭WebSocket连接

    7.1.定义 7.1.1.关闭WebSocket连接 为_关闭WebSocket连接_,端点需关闭底层TCP连接.端点应该使用一个方法完全地关闭TCP连接,以及TLS会话,如果合适,丢弃任何可能已经接 ...

  8. java集合类深入分析之Queue篇&lpar;Q&comma;DQ&rpar;

    简介 Queue是一种很常见的数据结构类型,在java里面Queue是一个接口,它只是定义了一个基本的Queue应该有哪些功能规约.实际上有多个Queue的实现,有的是采用线性表实现,有的基于链表实现 ...

  9. DevOps之服务故障

    唠叨话 关于德语噢屁事的知识点,仅提供精华汇总,具体知识点细节,参考教程网址,如需帮助,请留言. <DevOps服务故障> 0.故障1.设施故障2.设备故障3.系统故障4.虚拟化故障5.存 ...

  10. netty详解之reactor模型

    假设在办理各种证件时分为填表,审核,制作3个过程,每个过程用时10分钟,这样一个工作人员需要30分钟办理一个证件.那么有没有办法提供效率,减少等待时间呢.可以让一个专门的工作人员,每个顾客到来时就负责 ...