当前位置: 首页 > 工具软件 > AutoMapper > 使用案例 >

java automapper 使用_AutoMapper 使用总结

宋烨烁
2023-12-01

初识AutoMapper

在开始本篇文章之前,先来思考一个问题:一个项目分多层架构,如显示层、业务逻辑层、服务层、数据访问层。层与层访问需要数据载体,也就是类。如果多层通用一个类,一则会暴露出每层的字段,二者会使类字段很多,而且会出现很多冗余字段,这种方式是不可取的;如果每层都使用不同的类,则层与层调用时,一个字段一个字段的赋值又会很麻烦。针对第二种情况,可以使用AutoMapper来帮助我们实现类字段的赋值及转换。

AutoMapper是一个对象映射器,它可以将一个一种类型的对象转换为另一种类型的对象。AutoMapper提供了映射规则及操作方法,使我们不用过多配置就可以映射两个类。

安装AutoMapper

通过Nuget安装AutoMapper,本次使用版本为6.2.2。

AutoMapper配置

初始化

先创建两个类用于映射:

public class ProductEntity

{

public string Name { get; set; }

public decimal Amount { get; set; }

}

public class ProductDTO

{

public string Name { get; set; }

public decimal Amount { get; set; }

}

Automapper可以使用静态类和实例方法来创建映射,下面分别使用这两种方式来实现 ProductEntity -> ProductDTO的映射。

使用静态方式

Mapper.Initialize(cfg => cfg.CreateMap());

var productDTO = Mapper.Map(productEntity);

使用实例方法

MapperConfiguration configuration = new MapperConfiguration(cfg => cfg.CreateMap());

var mapper = configuration.CreateMapper();

var productDTO = mapper.Map(productEntity);

完整的例子:

[TestMethod]

public void TestInitialization()

{

var productEntity = new ProductEntity()

{

Name = "Product" + DateTime.Now.Ticks,

Amount = 10

};

Mapper.Initialize(cfg => cfg.CreateMap());

var productDTO = Mapper.Map(productEntity);

Assert.IsNotNull(productDTO);

Assert.IsNotNull(productDTO.Name);

Assert.IsTrue(productDTO.Amount > 0);

}

Profiles设置

除了使用以上两总方式类配置映射关系,也可以使用Profie配置来实现映射关系。

创建自定义的Profile需要继承Profile类:

public class MyProfile : Profile

{

public MyProfile()

{

CreateMap();

// Other mapping configurations

}

}

完成例子:

[TestMethod]

public void TestProfile()

{

var productEntity = new ProductEntity()

{

Name = "Product" + DateTime.Now.Ticks,

Amount = 10

};

var configuration = new MapperConfiguration(cfg => cfg.AddProfile());

var productDTO = configuration.CreateMapper().Map(productEntity);

Assert.IsNotNull(productDTO);

Assert.IsNotNull(productDTO.Name);

Assert.IsTrue(productDTO.Amount > 0);

}

除了使用AddProfile,也可以使用AddProfiles添加多个配置;同样,可以同时使用Mapper和Profile,也可以添加多个配置:

var configuration = new MapperConfiguration(cfg =>

{

cfg.AddProfile();

cfg.CreateMap();

});

扁平化映射

AutoMapper先映射名字一致的字段,如果没有,则会尝试使用以下规则来映射:

目标中字段去掉前缀“Get”后的部分

分割目标字段(根据Pascal命名方式)为单个单词

先创建用到的映射类:

public class Product

{

public Supplier Supplier { get; set; }

public string Name { get; set; }

public decimal GetAmount()

{

return 10;

}

}

public class Supplier

{

public string Name { get; set; }

}

public class ProductDTO

{

public string SupplierName { get; set; }

public decimal Amount { get; set; }

}

AutoMapper会自动实现Product.Supplier.Name -> ProductDTO.SupplierName, Product.GetTotal -> ProductDTO.Total的映射。

[TestMethod]

public void TestFalttening()

{

var supplier = new Supplier()

{

Name = "Supplier" + DateTime.Now.Ticks

};

var product = new Product()

{

Supplier = supplier,

Name = "Product" + DateTime.Now.Ticks

};

Mapper.Initialize(cfg => cfg.CreateMap());

var productDTO = Mapper.Map(product);

Assert.IsNotNull(productDTO);

Assert.IsNotNull(productDTO.SupplierName);

Assert.IsTrue(productDTO.Amount > 0);

}

集合验证

AutoMapper除了可以映射单个对象外,也可以映射集合对象。AutoMapper源集合类型支持以下几种:

IEnumerable

IEnumerable

ICollection

ICollection

IList

IList

List

Arrays

简单类型映射:

public class Source

{

public int Value { get; set; }

}

public class Destination

{

public int Value { get; set; }

}

[TestMethod]

public void TestCollectionSimple()

{

Mapper.Initialize(cfg => cfg.CreateMap());

var sources = new[]

{

new Source {Value = 1},

new Source {Value = 2},

new Source {Value = 3}

};

IEnumerable ienumerableDest = Mapper.Map>(sources);

ICollection icollectionDest = Mapper.Map>(sources);

IList ilistDest = Mapper.Map>(sources);

List listDest = Mapper.Map>(sources);

Destination[] arrayDest = Mapper.Map(sources);

}

复杂对象映射:

public class Order

{

private IList _lineItems = new List();

public OrderLine[] LineItems { get { return _lineItems.ToArray(); } }

public void AddLineItem(OrderLine orderLine)

{

_lineItems.Add(orderLine);

}

}

public class OrderLine

{

public int Quantity { get; set; }

}

public class OrderDTO

{

public OrderLineDTO[] LineItems { get; set; }

}

public class OrderLineDTO

{

public int Quantity { get; set; }

}

[TestMethod]

public void TestCollectionNested()

{

Mapper.Initialize(cfg =>

{

cfg.CreateMap();

cfg.CreateMap();

});

var order = new Order();

order.AddLineItem(new OrderLine {Quantity = 10});

order.AddLineItem(new OrderLine {Quantity = 20});

order.AddLineItem(new OrderLine {Quantity = 30});

var orderDTO = Mapper.Map(order);

Assert.IsNotNull(orderDTO);

Assert.IsNotNull(orderDTO.LineItems);

Assert.IsTrue(orderDTO.LineItems.Length > 0);

}

投影及条件映射

投影(指定字段)

除了以上使用的自动映射规则,AutoMapper还可以指定映射方式。下面使用ForMemeber指定字段的映射,将一个时间值拆分映射到日期、时、分:

public class Calendar

{

public DateTime CalendarDate { get; set; }

public string Title { get; set; }

}

public class CalendarModel

{

public DateTime Date { get; set; }

public int Hour { get; set; }

public int Minute { get; set; }

public string Title { get; set; }

}

[TestMethod]

public void TestProjection()

{

var calendar = new Calendar()

{

Title = "2018年日历",

CalendarDate = new DateTime(2018, 1, 1, 11, 59, 59)

};

Mapper.Initialize(cfg => cfg

.CreateMap()

.ForMember(dest => dest.Date, opt => opt.MapFrom(src =>src.CalendarDate.Date))

.ForMember(dest => dest.Hour, opt => opt.MapFrom(src => src.CalendarDate.Hour))

.ForMember(dest => dest.Minute, opt => opt.MapFrom(src => src.CalendarDate.Minute)));

var calendarModel = Mapper.Map(calendar);

Assert.AreEqual(calendarModel.Date.Ticks, new DateTime(2018, 1, 1).Ticks);

Assert.AreEqual(calendarModel.Hour, 11);

Assert.AreEqual(calendarModel.Minute, 59);

}

条件映射

有些情况下,我们会考虑添加映射条件,比如,某个值不符合条件时,不允许映射。针对这种情况可以使用ForMember中的Condition:

public class Source

{

public int Value { get; set; }

}

public class Destination

{

public uint Value { get; set; }

}

[TestMethod]

public void TestConditionByCondition()

{

var source = new Source()

{

Value = 3

};

//如果Source.Value > 0, 则执行映射;否则,映射失败

Mapper.Initialize(cfg => cfg

.CreateMap()

.ForMember(dest => dest.Value, opt => opt.Condition(src => src.Value > 0)));

var destation = Mapper.Map(source); //如果不符合条件,则抛出异常

Assert.IsTrue(destation.Value.Equals(3));

}

如果要映射的类符合一定的规则,而且有很多,针对每个类都创建一个CreaterMapper会很麻烦。可以使用AddConditionalObjectMapper指定对象映射规则,这样就不用每个映射关系都添加一个CreateMapper。另外,也可以使用AddMemberConfiguration指定字段的映射规则,比如字段的前后缀:

public class Product

{

public string Name { get; set; }

public int Count { get; set; }

}

public class ProductModel

{

public string NameModel { get; set; }

public int CountMod { get; set; }

}

[TestMethod]

public void TestConditionByConfiguration()

{

var product = new Product()

{

Name = "Product" + DateTime.Now.Ticks,

Count = 10

};

var config = new MapperConfiguration(cfg =>

{

//对象映射规则: 通过以下配置,可以映射所有”目标对象的名称“等于“源对象名称+Model”的类,而不用单个添加CreateMapper映射

cfg.AddConditionalObjectMapper().Where((s, d) => d.Name == s.Name + "Model");

//字段映射规则: 通过以下配置,可以映射“源字段”与“目标字段+Model或Mod”的字段

cfg.AddMemberConfiguration().AddName(_ => _.AddStrings(p => p.DestinationPostfixes, "Model", "Mod"));

});

var mapper = config.CreateMapper();

var productModel = mapper.Map(product);

Assert.IsTrue(productModel.CountMod == 10);

}

需要注意的一点是,添加了以上配置,如果目标对象中有字段没有映射到,则会抛出异常。

值转换

如果配置了值转换,AutoMapper会将修改转换后的值以符合配置的规则。比如,配置目标对象中的值添加符号“@@”:

public class Source

{

public string Name { get; set; }

}

public class Destination

{

public string Name { get; set; }

}

[TestMethod]

public void TestValueTransfer()

{

var source = new Source()

{

Name = "Bob"

};

Mapper.Initialize(cfg =>

{

cfg.CreateMap();

cfg.ValueTransformers.Add(val => string.Format("@{0}@", val));

});

var destation = Mapper.Map(source);

Assert.AreEqual("@Bob@", destation.Name);

}

空值替换

如果要映射的值为Null,则可以使用NullSubstitute指定Null值的替换值:

public class Source

{

public string Name { get; set; }

}

public class Destination

{

public string Name { get; set; }

}

[TestMethod]

public void TestValueTransfer()

{

var source = new Source()

{

};

Mapper.Initialize(cfg =>

{

cfg.CreateMap()

.ForMember(dest => dest.Name, opt => opt.NullSubstitute("其他值"));

});

var destation = Mapper.Map(source);

Assert.AreEqual("其他值", destation.Name);

}

配置验证及设置

配置了映射,但是如何确定是否映射成功或者是否有字段没有映射呢?可以添加Mapper.AssertConfigurationIsValid();来验证是否映射成功。默认情况下,目标对象中的字段都被映射到后,AssertConfigurationIsValid才会返回True。也就是说,源对象必须包含所有目标对象,这样在大多数情况下不是我们想要的,我们可以使用下面的方法来指定验证规则:

指定单个字段不验证

指定整个Map验证规则

public class Product

{

public string Name { get; set; }

public int Amount { get; set; }

}

public class ProductModel

{

public string Name { get; set; }

public int Amount { get; set; }

public string ViewName { get; set; }

}

public class ProductDTO

{

public string Name { get; set; }

public int Amount { get; set; }

public string ViewName { get; set; }

}

[TestMethod]

public void TestValidation()

{

var product = new Product()

{

Name = "Product" + DateTime.Now.Ticks,

Amount = 10

};

Mapper.Initialize(cfg =>

{

//1. 指定字段映射方式

cfg.CreateMap()

.ForMember(dest => dest.ViewName, opt => opt.Ignore()); //如果不添加此设置,会抛出异常

//2. 指定整个对象映射方式

//MemberList:

// Source: 检查源对象所有字段映射成功

// Destination:检查目标对象所有字段映射成功

// None: 跳过验证

cfg.CreateMap(MemberList.Source);

});

var productModel = Mapper.Map(product);

var productDTO = Mapper.Map(product);

//验证映射是否成功

Mapper.AssertConfigurationIsValid();

}

设置转换前后行为

有的时候你可能会在创建映射前后对数据做一些处理,AutoMapper就提供了这种方式:

public class Source

{

public string Name { get; set; }

public int Value { get; set; }

}

public class Destination

{

public string Name { get; set; }

public int Value { get; set; }

}

[TestMethod]

public void TestBeforeOrAfter()

{

var source = new Source()

{

Name = "Product" + DateTime.Now.Ticks,

};

Mapper.Initialize(cfg =>

{

cfg.CreateMap()

.BeforeMap((src, dest) => src.Value = src.Value + 10)

.AfterMap((src, dest) => dest.Name = "Pobin");

});

var productModel = Mapper.Map(source);

Assert.AreEqual("Pobin", productModel.Name);

}

反向映射

从6.1.0开始,AutoMapper通过调用Reverse可以实现反向映射。反向映射根据初始化时创建的正向映射规则来做反向映射:

public class Order

{

public decimal Total { get; set; }

public Customer Customer { get; set; }

}

public class Customer

{

public string Name { get; set; }

}

public class OrderDTO

{

public decimal Total { get; set; }

public string CustomerName { get; set; }

}

[TestMethod]

public void TestReverseMapping()

{

var customer = new Customer

{

Name = "Tom"

};

var order = new Order

{

Customer = customer,

Total = 20

};

Mapper.Initialize(cfg => {

cfg.CreateMap()

.ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.Name)) //正向映射规则

.ReverseMap(); //设置反向映射

});

//正向映射

var orderDTO = Mapper.Map(order);

//反向映射:使用ReverseMap,不用再创建OrderDTO -> Order的映射,而且还能保留正向的映射规则

var orderConverted = Mapper.Map(orderDTO);

Assert.IsNotNull(orderConverted.Customer);

Assert.AreEqual("Tom", orderConverted.Customer.Name);

}

如果反向映射中不想使用原先的映射规则,也可以取消掉:

Mapper.Initialize(cfg => {

cfg.CreateMap()

.ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.Name)) //正向映射规则

.ReverseMap()

.ForPath(src => src.Customer.Name, opt => opt.Ignore()); //设置反向映射

});

自定义转换器

有些情况下目标字段类型和源字段类型不一致,可以通过类型转换器实现映射,类型转换器有三种实现方式:

void ConvertUsing(Func mappingFunction);

void ConvertUsing(ITypeConverter converter);

void ConvertUsing() where TTypeConverter : ITypeConverter;

下面通过一个例子来演示下以上三种类型转换器的使用方式:

namespace AutoMapperSummary

{

[TestClass]

public class CustomerTypeConvert

{

public class Source

{

public string Value1 { get; set; }

public string Value2 { get; set; }

public string Value3 { get; set; }

}

public class Destination

{

public int Value1 { get; set; }

public DateTime Value2 { get; set; }

public Type Value3 { get; set; }

}

public class DateTimeTypeConverter : ITypeConverter

{

public DateTime Convert(string source, DateTime destination, ResolutionContext context)

{

return System.Convert.ToDateTime(source);

}

}

public class TypeTypeConverter : ITypeConverter

{

public Type Convert(string source, Type destination, ResolutionContext context)

{

return Assembly.GetExecutingAssembly().GetType(source);

}

}

[TestMethod]

public void TestTypeConvert()

{

var config = new MapperConfiguration(cfg =>

{

cfg.CreateMap().ConvertUsing((string s) => Convert.ToInt32(s));

cfg.CreateMap().ConvertUsing(new DateTimeTypeConverter());

cfg.CreateMap().ConvertUsing();

cfg.CreateMap();

});

config.AssertConfigurationIsValid(); //验证映射是否成功

var source = new Source

{

Value1 = "20",

Value2 = "2018/1/1",

Value3 = "AutoMapperSummary.CustomerTypeConvert+Destination"

};

var mapper = config.CreateMapper();

var destination = mapper.Map(source);

Assert.AreEqual(typeof(Destination), destination.Value3);

}

}

}

自定义解析器

使用AutoMapper的自带解析规则,我们可以很方便的实现对象的映射。比如:源/目标字段名称一致,“Get/get + 源字段“与"目标字段"一致等。除了这些简单的映射,还可以使用ForMember指定字段映射。但是,某些情况下,解析规则会很复杂,使用自带的解析规则无法实现。这时可以自定义解析规则,可以通过以下三种方式使用自定义的解析器:

ResolveUsing

ResolveUsing(typeof(CustomValueResolver))

ResolveUsing(aValueResolverInstance)

下面通过一个例子来演示如何使用自定义解析器:

public class Source

{

public string FirstName { get; set; }

public string LastName { get; set; }

}

public class Destination

{

public string Name { get; set; }

}

///

/// 自定义解析器: 组合姓名

///

public class CustomResolver : IValueResolver

{

public string Resolve(Source source, Destination destination, string destMember, ResolutionContext context)

{

if (source != null && !string.IsNullOrEmpty(source.FirstName) && !string.IsNullOrEmpty(source.LastName))

{

return string.Format("{0} {1}", source.FirstName, source.LastName);

}

return string.Empty;

}

}

[TestMethod]

public void TestResolver()

{

Mapper.Initialize(cfg =>

cfg.CreateMap()

.ForMember(dest => dest.Name, opt => opt.ResolveUsing()));

Mapper.AssertConfigurationIsValid();

var source = new Source

{

FirstName = "Michael",

LastName = "Jackson"

};

var destination = Mapper.Map(source);

Assert.AreEqual("Michael Jackson", destination.Name);

}

AutoMapper封装

AutoMapper功能很强大,自定义配置支持也非常好,但是真正项目中使用时却很少用到这么多功能,而且一般都会对AutoMapper进一步封装使用。一方面使用起来方面,另外一方面也可以使代码统一。下面的只是做一个简单的封装,还需要结合实际项目使用:

///

/// AutoMapper帮助类

///

public class AutoMapperManager

{

private static readonly MapperConfigurationExpression MapperConfiguration = new MapperConfigurationExpression();

static AutoMapperManager()

{

}

private AutoMapperManager()

{

AutoMapper.Mapper.Initialize(MapperConfiguration);

}

public static AutoMapperManager Instance { get; } = new AutoMapperManager();

///

/// 添加映射关系

///

///

///

public void AddMap() where TSource : class, new() where TDestination : class, new()

{

MapperConfiguration.CreateMap();

}

///

/// 获取映射值

///

///

///

///

public TDestination Map(object source) where TDestination : class, new()

{

if (source == null)

{

return default(TDestination);

}

return Mapper.Map(source);

}

///

/// 获取集合映射值

///

///

///

///

public IEnumerable Map(IEnumerable source) where TDestination : class, new()

{

if (source == null)

{

return default(IEnumerable);

}

return Mapper.Map>(source);

}

///

/// 获取映射值

///

///

///

///

///

public TDestination Map(TSource source) where TSource : class, new () where TDestination : class, new()

{

if (source == null)

{

return default(TDestination);

}

return Mapper.Map(source);

}

///

/// 获取集合映射值

///

///

///

///

///

public IEnumerable Map(IEnumerable source) where TSource : class, new() where TDestination : class, new()

{

if (source == null)

{

return default(IEnumerable);

}

return Mapper.Map, IEnumerable>(source);

}

///

/// 读取DataReader内容

///

///

///

///

public IEnumerable Map(IDataReader reader)

{

if (reader == null)

{

return new List();

}

var result = Mapper.Map>(reader);

if (!reader.IsClosed)

{

reader.Close();

}

return result;

}

}

总结

本篇文章列举了AutoMapper的基本使用方式,更多的使用可以参考官方文档:http://automapper.readthedocs.io/en/latest/index.html

 类似资料: