【.NET】AutoMapper学习记录
在两个不同的类型对象之间传输数据,通常我们会用DTOs(数据传输对象),AutoMapper就是将一个对象自动转换为另一个对象的技术
背景
一些orm框架,在用到Entity的时候有一些开源代码用到了automapper(如:nopcommence),将数据对象转成DTO。比如在ORM中,与数据库交互用的Model模型是具有很多属性变量方法神马的。而当我们与其它系统(或系统中的其它结构)进行数据交互时,出于耦合性考虑或者安全性考虑或者性能考虑(总之就是各种考虑),我们不希望直接将这个Model模型传递给它们,这时我们会创建一个贫血模型来保存数据并传递。什么是贫血模型?贫血模型(DTO,Data Transfer Object)就是说只包含属性什么的,只能保存必须的数据,没有其它任何的多余的方法数据什么的,专门用于数据传递用的类型对象。在这个创建的过程中,如果我们手动来进行,就会看到这样的代码:
A a=new A();
a.X1=b.X1;
a.X2=b.X2;
...
...
...
return a; 太麻烦
此时,AutoMapper可以发挥的作用就是根据A的模型和B的模型中的定义,自动将A模型映射为一个全新的B模型。(不用一个属性一个属性的赋值)
好处:
1、 db或者模型 增加字段时,只需在DTO内部增加映射,赋值代码无需修改
2、隔离,前端收集各参数,不用管后端定义的模型。前后端才用AutoMapper来做转换。
使用
Nuget引用:AutoMapper 版本不一样,里面的很多方法有些不一样
AutoMapper是基于约定的,因此在实用映射之前,我们需要先进行映射规则的配置。
我们要做的只是将要映射的两个类型告诉AutoMapper(调用Mapper类的Static方法CreateMap并传入要映射的类型):
Mapper.Initialize(cfg => { cfg.CreateMap<StudentEntity, StudentOutput>(); });
也可以将实体类 放在配置文件MapperProfile中
Mapper.Initialize(cfg => {
cfg.AddProfile<MapperProfile>();
cfg.AddProfile<ProxyAdapterProfile>(); //可增加多个
});
注意:多次调用 Mapper.Initialize() 只有最后一次生效。所以只能用一个Mapper.Initialize。
【AutoMapper.7.0.1】
class MapperProfile : Profile { public MapperProfile() { CreateMap<StudentEntity, StudentOutput>(); var map = CreateMap<UploadResponseBase, UploadResult>(); //字段名称不一致,一次直接定义好所有字段的映射规则 map.ConvertUsing(s => new UploadResult { IsSuccess = s.success, FileUrl = s.clientUrl, ErrorMessage = s.rawFileName , datetimeStr = (s.datetime).ToString(), }); } }
【AutoMapper 4.2.1.0】
AutoMapper使用ForMember来指定每一个字段的映射规则:
protected override void Configure() { var mapResponst = CreateMap<Response, CResponse>(); mapResponst.ForMember(dest => dest.departure_date, opt => opt.MapFrom(src => src.DepartureDate.ToString("yyyy-MM-dd HH:mm:ss"))) .ForMember(dest => dest.ticket_price, opt => opt.MapFrom(src => src.TicketPrice)); var mapContacts = CreateMap<CContacts, PassengerInputEntity>(); mapContacts.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.First_Name)) .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.Last_Name)) .ForMember(dest => dest.AreaCode, opt => opt.MapFrom(src => src.Area_Code)); }
然后就可以交给AutoMapper帮我们搞定一切了:
//实例化实体List
List<StudentEntity> StudentList = new List<StudentEntity>();
//模拟数据
StudentList.Add(new StudentEntity
{
Id = 1,
Age = 12,
Gander = "boy",
Name = "WangZeLing",
Say = "Only the paranoid survive",
Score = 99M
});
//AuotMapper具体使用方法 将List<StudentEntity>转换为List<StudentOutput>
List<StudentOutput> Output = Mapper.Map<List<StudentOutput>>(StudentList);
Output.ForEach(output => Console.WriteLine(string.Format("name:{0},say:{1},score:{2}", output.Name, output.Say, output.Score)));
解释
1、 AutoMapper给我们提供的Convention或Configuration方式并不是“异或的”,我们可以结合使用两种方式,为名称不同的字段配置映射规则,而对于名称相同的字段则忽略配置。
2、 在映射具有相同字段名的类型时,会自动转换
3、 不相同名称的属性则需要 指定映射字段,设置ConvertUsing或者ForMember..
4、 值为空的属性,AutoMapper在映射的时候会把相应属性也置为空
5、 如果传入一个空的AddressDto,AutoMapper也会帮我们得到一个空的Address对象。
Address address = Mapper.Map<AddressDto,Address>(null);
6、不需要映射的属性可以用Ignore忽略。【if有验证 目标类中的所有属性是否都被映射 时】
使用Ignore方法:
Mapper.CreateMap<Entity.Source, Entity.Destination>()
.ForMember(dest => dest.SomeValuefff, opt =>
{
opt.Ignore();
});
最佳实践
这段内容将讨论AutoMapper的规则写在什么地方的问题。
在上一段中,我们已经知道了如何使用AutoMapper进行简单的对象映射,但是,在实际的项目中,我们会有很多类进行映射(从Entity转换为Dto,或者从Entity转换为ViewModel等),这么多的映射如何组织将成为一个问题。
首先我们需要定义一个Configuration.cs的类,该类提供AutoMapper规则配置的入口,它只提供一个静态的方法,在程序第一次运行的时候调用该方法完成配置。
当有多个Profile的时候,我们可以这样添加:
public class Configuration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<Profiles.SourceProfile>();
cfg.AddProfile<Profiles.OrderProfile>();
cfg.AddProfile<Profiles.CalendarEventProfile>();
});
}
}
在程序运行的时候,只需要调用Configure方法即可。
了解了这些实现以后,我们可以再项目中添加AutoMapper文件夹。
Configuration为我们的静态配置入口类;Profiles文件夹为我们所有Profile类的文件夹。如果是MVC,我们需要在Global中调用:
AutoMapper.Configuration.Configure();
问题:Missing type map configuration or unsupported mapping
重现:本地调试直接打开出错的页面,调试发现是ok的;然后先打开用到了mapper所在控制器对应的页面,再去打开出错的页面,是报错的。
从 GitHub 上签出 AutoMapper 的源代码一看 Mapper.Initialize() 的实现,恍然大悟。
public static void Initialize(Action<IMapperConfigurationExpression> config)
{
Configuration = new MapperConfiguration(config);
Instance = new Mapper(Configuration);
}
原来每次调用 Mapper.Initialize() 都会创建新的 Mapper 实例,也就是多次调用 Mapper.Initialize() 只有最后一次生效。
切记不要多处调用Mapper.Initialize()。
优化方法:【写一个工具类】
需程序集:AutoMapper
/// <summary> /// 优化AutoMap映射工具,解决AutoMap只能Initialize一次的问题 /// </summary> public class AutoMapperManager { /// <summary> /// 存储所有的profile /// </summary> static ConcurrentBag<Profile> Profiles; static AutoMapperManager() { Profiles = new ConcurrentBag<Profile>(); } /// <summary> /// 新增Profile,必須放在靜態構造函數里 /// </summary> /// <param name="profile"></param> public static void AddProfile(Profile profile) { Profiles.Add(profile); } /// <summary> /// 初始化,可以多次调用,同时之前的Profile也会生效 /// </summary> public static void Initialize() { Mapper.Initialize(config => { Profiles.ToList().ForEach(file => { config.AddProfile(file); }); }); } }
其他地方需要用mapper的地方 调用方式:
AutoMapperManager.AddProfile(new Profile1());
AutoMapperManager.AddProfile(new Profile2());
AutoMapperManager.Initialize();
参考:
https://www.cnblogs.com/jobs2/p/3503990.html
https://www.cnblogs.com/youring2/p/automapper.html
http://www.cnblogs.com/dudu/p/5875579.html
【.NET】AutoMapper学习记录的更多相关文章
- Quartz 学习记录1
原因 公司有一些批量定时任务可能需要在夜间执行,用的是quartz和spring batch两个框架.quartz是个定时任务框架,spring batch是个批处理框架. 虽然我自己的小玩意儿平时不 ...
- Java 静态内部类与非静态内部类 学习记录.
目的 为什么会有这篇文章呢,是因为我在学习各种框架的时候发现很多框架都用到了这些内部类的小技巧,虽然我平时写代码的时候基本不用,但是看别人代码的话至少要了解基本知识吧,另外到底内部类应该应用在哪些场合 ...
- Apache Shiro 学习记录4
今天看了教程的第三章...是关于授权的......和以前一样.....自己也研究了下....我觉得看那篇教程怎么说呢.....总体上是为数不多的精品教程了吧....但是有些地方确实是讲的太少了.... ...
- UWP学习记录12-应用到应用的通信
UWP学习记录12-应用到应用的通信 1.应用间通信 “共享”合约是用户可以在应用之间快速交换数据的一种方式. 例如,用户可能希望使用社交网络应用与其好友共享网页,或者将链接保存在笔记应用中以供日后参 ...
- UWP学习记录11-设计和UI
UWP学习记录11-设计和UI 1.输入和设备 通用 Windows 平台 (UWP) 中的用户交互组合了输入和输出源(例如鼠标.键盘.笔.触摸.触摸板.语音.Cortana.控制器.手势.注视等)以 ...
- UWP学习记录10-设计和UI之控件和模式7
UWP学习记录10-设计和UI之控件和模式7 1.导航控件 Hub,中心控件,利用它你可以将应用内容整理到不同但又相关的区域或类别中. 中心的各个区域可按首选顺序遍历,并且可用作更具体体验的起始点. ...
- UWP学习记录9-设计和UI之控件和模式6
UWP学习记录9-设计和UI之控件和模式6 1.图形和墨迹 InkCanvas是接收和显示墨迹笔划的控件,是新增的比较复杂的控件,这里先不深入. 而形状(Shape)则是可以显示的各种保留模式图形对象 ...
- UWP学习记录8-设计和UI之控件和模式5
UWP学习记录8-设计和UI之控件和模式5 1.日历.日期和时间控件 日期和时间控件提供了标准的本地化方法,可供用户在应用中查看并设置日期和时间值. 有四个日期和时间控件可供选择,选择的依据如下: 日 ...
- UWP学习记录7-设计和UI之控件和模式4
UWP学习记录7-设计和UI之控件和模式4 1.翻转视图 使用翻转视图浏览集合中的图像或其他项目(例如相册中的照片或产品详细信息页中的项目),一次显示一个项目. 对于触摸设备,轻扫某个项将在整个集合中 ...
随机推荐
- cocos代码研究(10)ActionEase子类学习笔记
理论部分 缓动动作的基类,继承自 ActionInterval类.ActionEase本身是一个抽象的概念父类,开发者最好不要在代码中直接创建它的对象,因为它没有具体的执行效果,这一类的子类速度变化大 ...
- 234. Palindrome Linked List(判断链表是否回文)
Given a singly linked list, determine if it is a palindrome. Follow up:Could you do it in O(n) time ...
- 2018年浙江中医药大学程序设计竞赛 Solution
Problem A. Jhadgre的C语言程序 签. #include <bits/stdc++.h> using namespace std; int main() { puts(&q ...
- oracle 11g 数据库中报:协议适配器错误
本人遇到该问题,到数据库服务器上重新启动监听和实例就OK了.
- 2017-2018-1 JaWorld 团队作业--冲刺5
2017-2018-1 JaWorld 团队作业--冲刺5(20162310) 团队项目之战斗机类分析博客 总结 我们本次团队项目设定为基于Android系统Java架构下的打飞机小游戏 游戏中所有模 ...
- 【前端】特效-Javascript实现购物页面图片放大效果
实现效果 实现代码: <!DOCTYPE html> <html> <head> <title>购物图片放大</title> <met ...
- git clone时,提示warning: remote HEAD refers to nonexistent ref, unable to checkout
一.环境 发行版:Ubuntu 18.04.1 LTS 代号:bionic 内核版本:4.15.0-30-generic 二.背景 git clone https://source.codeauror ...
- root权限和sudo得到权限的区别
参考: 知乎 命令前加sudo执行和用真正的root用户执行有什么区别?pansz的回答 root用户和sudo使用root权限的区别 变换用户身份为root的方法su 与 sudo root权限和s ...
- springboot p6spy 打印完整sql
调试时打印出sql的需求,太正常不过了,mybatis也提供了这样的功能: mybatis: configuration: log-impl: org.apache.ibatis.logging.st ...
- Java中的组合与聚合
组合和聚合是有很大区别的,这个区别不是在形式上,而是在本质上:比如A类中包含B类的一个引用b,当A类的一个对象消亡时,b这个引用所指向的对象也同时消亡(没有任何一个引用指向它,成了垃圾对象),这种情况 ...