AutoMapper是对象到对象的映射工具。在完成映射规则之后,AutoMapper可以将源对象转换为目标对象。

配置AutoMapper映射规则

AutoMapper是基于约定的,因此在实用映射之前,我们需要先进行映射规则的配置。

public class Source
{
public int SomeValue { get; set; }
public string AnotherValue { get; set; }
} public class Destination
{
public int SomeValue { get; set; }
}

在上面的代码中,我们定义了两个类,我们需要将Source类的对象映射到Destination类的对象上面。要完成这个操作,我们需要对AutoMapper进行如下配置:

Mapper.CreateMap<Source, Destination>();

进行一下测试:

Source src = new Source() { SomeValue = 1, AnotherValue = "2" };
Destination dest = Mapper.Map<Destination>(src); ObjectDumper.Write(dest);

我们可以在控制台看到dest对象的属性值:

这样我们就完成了一个简单的AutoMapper映射。

Profile的用法

Profile提供了一个命名的映射类,所有继承自Profile类的子类都是一个映射集合。

我们来看一下Profile的用法,这个例子中仍然使用上面的Source类和Destination类。

public class SourceProfile : Profile
{
protected override void Configure()
{
CreateMap<Source, Destination>();
}
}

我们可以再Profile中重写Configure方法,从而完成映射规则的配置。从Profile初始化Mapper规则:

Mapper.Initialize(x => x.AddProfile<SourceProfile>());

在一个Profile中,我们可以完成多个、更复杂的规则的约定:

public class Destination2
{
public int SomeValue { get; set; }
public string AnotherValue2 { get; set; }
} public class SourceProfile : Profile
{
protected override void Configure()
{
//Source->Destination
CreateMap<Source, Destination>(); //Source->Destination2
CreateMap<Source, Destination2>().ForMember(d => d.AnotherValue2, opt =>
{
opt.MapFrom(s => s.AnotherValue);
});
}
}

AutoMapper最佳实践

这段内容将讨论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();

扁平化映射(Flattening)

默认情况下,我们的Source类和Destination类是根据属性名称进行匹配映射的。除此之外,默认的映射规则还有下面两种情况,我们称之为扁平化映射,即当Source类中不包含Destination类中的属性的时候,AutoMapper会将Destination类中的属性进行分割,或匹配“Get”开头的方法,例如:

Order类:

public class Order
{
public Customer Customer { get; set; } public decimal GetTotal()
{
return 100M;
}
}

Order类中包含了一个customer对象和一个GetTotal方法,为了方便演示,我直接将GetTotal方法返回100;

Customer类的定义如下:

public class Customer
{
public string Name { get; set; }
}

OrderDto类的定义如下:

public class OrderDto
{
public string CustomerName { get; set; }
public string Total { get; set; }
}

我们在进行映射的时候,不需要进行特殊的配置,既可以完成从Order到OrderDto的映射。

public class OrderProfile : Profile
{
protected override void Configure()
{
CreateMap<Entity.Order, Dto.OrderDto>();
}
}

测试代码:

Entity.Customer customer = new Entity.Customer() { Name = "Tom" };
Entity.Order order = new Entity.Order() { Customer = customer };
Dto.OrderDto orderDto = Mapper.Map<Dto.OrderDto>(order);
ObjectDumper.Write(order, 2);
ObjectDumper.Write(orderDto);

测试结果:

指定映射字段(Projection)

在实际的业务环境中,我们的Source类和Destination类的字段不可能一对一的匹配,这个时候我们就需要来指定他们的实际映射关系,例如:

public class CalendarEvent
{
public DateTime Date { get; set; }
public string Title { get; set; }
} public class CalendarEventForm
{
public DateTime EventDate { get; set; }
public int EventHour { get; set; }
public int EventMinute { get; set; }
public string DisplayTitle { get; set; }
}

在这两个类中,CalendarEvent的Date将被拆分为CalendarEventForm的日期、时、分三个字段,Title也将对应DisplayTitle字段,那么相应的Profile定义如下:

public class CalendarEventProfile : Profile
{
protected override void Configure()
{
CreateMap<Entity.CalendarEvent, Entity.CalendarEventForm>()
.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.Date.Date))
.ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.Date.Hour))
.ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.Date.Minute))
.ForMember(dest => dest.DisplayTitle, opt => opt.MapFrom(src => src.Title));
}
}

测试代码:

Entity.CalendarEvent calendarEvent = new Entity.CalendarEvent()
{
Date = DateTime.Now,
Title = "Demo Event"
};
Entity.CalendarEventForm calendarEventForm = Mapper.Map<Entity.CalendarEventForm>(calendarEvent);
ObjectDumper.Write(calendarEventForm);

测试结果:

验证配置项(Configuration Validation)

AutoMapper提供了一种验证机制,用来判断Destination类中的所有属性是否都被映射,如果存在未被映射的属性,则抛出异常。

验证的用法:

Mapper.AssertConfigurationIsValid();

例如:

public class Source
{
public int SomeValue { get; set; }
public string AnotherValue { get; set; }
}

Destination代码:

public class Destination
{
public int SomeValuefff { get; set; }
}

测试:

Mapper.CreateMap<Entity.Source, Entity.Destination>();
Mapper.AssertConfigurationIsValid();

运行程序将会出现AutoMapperConfigurationException异常:

这是因为SomeValuefff在Source类中没有对应的字段造成的。

解决这种异常的方法有:

指定映射字段,例如:

Mapper.CreateMap<Entity.Source, Entity.Destination>()
.ForMember(dest => dest.SomeValuefff, opt =>
{
opt.MapFrom(src => src.SomeValue);
});

或者使用Ignore方法:

Mapper.CreateMap<Entity.Source, Entity.Destination>()
.ForMember(dest => dest.SomeValuefff, opt =>
{
opt.Ignore();
});

或者使用自定义解析器,自定义解析器在下面讲到。

自定义解析器(Custom value resolvers)

AutoMapper允许我们自定义解析器来完成Source到Destination的值的转换。例如:

public class Source
{
public int Value1 { get; set; }
public int Value2 { get; set; }
} public class Destination
{
public int Total { get; set; }
}

Total属性在Source中不存在,如果现在创建映射规则,在映射的时候必然会抛出异常。这个时候我们就需要使用自定义解析器来完成映射。

自定义解析器需要实现 IValueResolver 接口,接口的定义如下:

public interface IValueResolver
{
ResolutionResult Resolve(ResolutionResult source);
}

我们来自定义一个Resolver:

public class CustomResolver : ValueResolver<Source, int>
{
protected override int ResolveCore(Source source)
{
return source.Value1 + source.Value2;
}
}

然后在映射规则中使用这个解析器:

public class SourceProfile : Profile
{
protected override void Configure()
{
//Source->Destination
CreateMap<Source, Destination>()
.ForMember(dest => dest.Total, opt =>
{
opt.ResolveUsing<CustomResolver>();
});
}
}

测试代码:

Source src = new Source()
{
Value1 = 1,
Value2 = 2
};
Destination dest = Mapper.Map<Destination>(src);
ObjectDumper.Write(dest);

测试结果:

在使用自定义Resolver中,我们还可以指定Resolver的构造函数,例如:

//Source->Destination
CreateMap<Source, Destination>()
.ForMember(dest => dest.Total, opt =>
{
opt.ResolveUsing<CustomResolver>()
.ConstructedBy(() =>

new CustomResolver

());
});

自定义类型转换器(Custom type converters)

AutoMapper通过ConvertUsing来使用自定义类型转换器。ConvertUsing有三种用法:

void ConvertUsing(Func<TSource, TDestination> mappingFunction);
void ConvertUsing(ITypeConverter<TSource, TDestination> converter);
void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination>;

当我们有如下的Source类和Destination类:

public class Source
{
public string Value1 { get; set; }
} public class Destination
{
public int Value1 { get; set; }
}

我们可以使用如下配置:

public class SourceProfile : Profile
{
protected override void Configure()
{
//string->int
CreateMap<string, int>()
.ConvertUsing(Convert.ToInt32);
//Source->Destination
CreateMap<Source, Destination>();
}
}

在上面的配置中,我们首先创建了从string到int的类型转换,这里使用了系统自带的Convert.ToInt32转换方法。

除了这种方法之外,我们还可以自定义类型转换器:

public class CustomConverter : ITypeConverter<Source, Destination>
{
public Destination Convert(ResolutionContext context)
{
Source src = context.SourceValue as Source;
Destination dest = new Destination();
dest.Value1 = System.Convert.ToInt32(src.Value1); return dest;
}
}

通过这个转换器,我们可以绕过string到int的转换,直接将Source类的对象转换为Destination类的对象。

对应的配置如下:

public class SourceProfile : Profile
{
protected override void Configure()
{
//Source->Destination
CreateMap<Source, Destination>()
.ConvertUsing<CustomConverter>();
}
}

或者,我们也可以使用下面的配置:

public class SourceProfile : Profile
{
protected override void Configure()
{
//Source->Destination
CustomConverter converter = new CustomConverter();
CreateMap<Source, Destination>()
.ConvertUsing(converter);
}
}

空值替换(Null substitution)

空值替换允许我们将Source对象中的空值在转换为Destination的值的时候,使用指定的值来替换空值。

public class Source
{
public string Value { get; set; }
} public class Destination
{
public string Value { get; set; }
}

配置代码:

public class SourceProfile : Profile
{
protected override void Configure()
{
//Source->Destination
CreateMap<Source, Destination>()
.ForMember(dest => dest.Value, opt =>
{
opt.NullSubstitute("原始值为NULL");
});
}
}

测试代码:

Source src = new Source();
Destination dest = Mapper.Map<Destination>(src);
ObjectDumper.Write(dest);

测试结果:

条件映射(Conditional mapping)

条件映射只当Source类中的属性值满足一定条件的时候才进行映射。例如:

public class Foo
{
public int baz;
} public class Bar
{
public uint baz;
}

对应的配置代码如下:

Mapper.CreateMap<Foo, Bar>()
.ForMember(dest => dest.baz, opt =>
{
opt.Condition(src => (src.baz >= 0));
});

总结

AutoMapper的一些常用方法都已经整理出来,代码也都在文章中,不再提供下载。

如果认为此文对您有帮助,别忘了支持一下哦!

作者:齐飞
声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权,贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。

AutoMapper用法 转载https://www.cnblogs.com/youring2/p/automapper.html的更多相关文章

  1. 初步认识AutoMapper转载 https://www.cnblogs.com/fred-bao/p/5700776.html

    初步认识AutoMapper AutoMapper 初步认识AutoMapper 前言 手动映射 使用AutoMapper 创建映射 Conventions 映射到一个已存在的实例对象   前言 通常 ...

  2. Bootstrap-table 使用总结 转载https://www.cnblogs.com/laowangc/p/8875526.html

    一.什么是Bootstrap-table? 在业务系统开发中,对表格记录的查询.分页.排序等处理是非常常见的,在Web开发中,可以采用很多功能强大的插件来满足要求,且能极大的提高开发效率,本随笔介绍这 ...

  3. Autofac框架详解 转载https://www.cnblogs.com/lenmom/p/9081658.html

    一.组件 创建出来的对象需要从组件中来获取,组件的创建有如下4种(延续第一篇的Demo,仅仅变动所贴出的代码)方式: 1.类型创建RegisterType AutoFac能够通过反射检查一个类型,选择 ...

  4. 转载 https://www.cnblogs.com/bobo-pcb/p/11708459.html

    https://www.cnblogs.com/bobo-pcb/p/11708459.html #### 1 用VMware 15.0+win10企业版 1次安装成功 20200124 2 不要用v ...

  5. 转载 c# automapper 使用(一) https://www.cnblogs.com/caoyc/p/6367828.html

    一.最简单的用法 有两个类User和UserDto 1 public class User 2 { 3 public int Id { get; set; } 4 public string Name ...

  6. sscanf 与 ssprintf 用法 (转载--https://www.cnblogs.com/Anker/p/3351168.html)

    sprintf函数 sprintf函数原型为 int sprintf(char *str, const char *format, ...).作用是格式化字符串,具体功能如下所示: (1)将数字变量转 ...

  7. django admin 设置(转载https://www.cnblogs.com/wumingxiaoyao/p/6928297.html)

    Django admin 一些有用的设置   Django自带的后台管理是Django明显特色之一,可以让我们快速便捷管理数据.后台管理可以在各个app的admin.py文件中进行控制.以下是我最近摸 ...

  8. ASP.NET MVC学习系列(二)-WebAPI请求 转载https://www.cnblogs.com/babycool/p/3922738.html

    继续接着上文 ASP.NET MVC学习系列(一)-WebAPI初探 来看看对于一般前台页面发起的get和post请求,我们在Web API中要如何来处理. 这里我使用Jquery 来发起异步请求实现 ...

  9. .NET Core开源组件:后台任务利器之Hangfire 转载 https://www.cnblogs.com/chenug/p/6655636.html

    .NET Core开源组件:后台任务利器之Hangfire   一.简述 Hangfire作为一款高人气且容易上手的分布式后台执行服务,支持多种数据库.在.net core的环境中,由Core自带的D ...

随机推荐

  1. 如何为mysql建立索引

    前些时候,一位颇高级的程序员居然问我什么叫做索引,令我感到十分的惊奇,我想这绝不会是沧海一粟,因为有成千上万的开发者(可能大部分是使用MySQL的)都没有受过有关数据库的正规培训,尽管他们都为客户做过 ...

  2. Shell逻辑运算符及表达式

    一. 运算符总结说明 1. 条件运算符 运算符号 代表意义 应用 说明 = 等于 整型或字符串比较: str1 = str2 字符串str1 和字符串str2 相等时返回真,如果在[]中,只能是字符串 ...

  3. django-redis-cache缓存使用

    1. redis安装配置 (1)到redis目录 [root@localhost redis-2.8.17]# ls 00-RELEASENOTES CONTRIBUTING deps Makefil ...

  4. python3反转列表的三种方式

    1.内建函数 reversed() li = [1,2,3,4,5,6] a = list(reversed(li)) print(a) 注意:reversed()函数返回的是一个迭代器,而不是一个L ...

  5. Spring clound 微服务--理解篇

    定义:微服务就是一些协调工作的小而自治的服务 优点: 异构性:不同微服务可以使用不同的语言实现, 后端数据库也可以根据自身业务定义服务. 弹性: 一个组件不可用,不会导致级联故障.一个系统出了问题,不 ...

  6. [Java 教程 04] Java基础语法

    在上一篇文章中我们已经运行了个简单的java程序,但是没有给大家讲解代码部分的内容与含义.学习,我们要做到知其然而知其所以然,所以本篇文章我们就来讲解java程序的基本语法,学完这篇文章你再回头看上篇 ...

  7. 对于一键退出APP功能实现的技术探讨

    在Android的开发过程中,会经常存在“一键退出APP”的需求.经过一段时间的整理,其主要实现方式有以下几种. 本质:一键结束当前APP的所有activity&一键结束当前APP进程,两者合 ...

  8. linux100day(day7)--用户管理和权限管理简单介绍

    系统基础 计算机的三大部件 CPU 内存 IO 总线 一般使用system call和api来调用硬件 一些基础命令, pwd 查看当前路径 cal 计算器 clock 时钟 hwclock 显示与设 ...

  9. linux---postgresql的用户角色权限

    PostgreSQL是通过角色来管理数据库访问权限的,我们可以将一个角色看成是一个数据库用户,或者一组数据库用户.角色可以拥有数据库对象,如表.索引,也可以把这些对象上的权限赋予其它角色,以控制哪些用 ...

  10. Codeforces 1190C Tokitsukaze and Duel game

    题意:有一个长为n的01串,两个人轮流操作,每个人可以把某个长度为m的区间变成相同颜色,谁在操作后整个串颜色相同就赢了.问最后是谁赢?(有可能平局) 思路:容易发现,如果第一个人不能一击必胜,那么他就 ...