在Web开发中常用的数据交换(赋值)中间件按照所对应的使用对象有两大类:
- 程序与数据库进行数据交换(赋值)中间件:Entity Framework (EF) Core、FluentMigrator。
- 程序与前台界面(UI)模型类进行数据交换(赋值)中间件:AutoMapper。
使用数据转换(赋值)中间件的N种理由
1、大型的工程性程序都是有多个擅长不同领域的开发者分工合作来完成的,擅长不同领域的开发者,把大型程序性程序进行拆分按照不同的层级模块进行开发。但在整个程序进行有机的整合时,由于不同层级的模块之间所进行数据交换时数据的定义不是完全相同的,为了保障整个程序能够在整合后可以正常的被执行,就需要通过不同应用场景的数据转换(赋值)中间件,来完成协助不同的层级模块之进行数据(赋值)的交换操作。
2、不同的层级模块之进行数据(赋值)的交换操作,当然开发者也可以不通过数据交换(赋值)中间件,由自己直接定义来完成,但你能保证自定义的代码比经过多个版本迭代的第3方数据转换(赋值)中间件功能更为强大、安全性更高,同时软件特别是一些实用性的软件都是工程性的产品,工程性的产品的1大特性就是集成现有的成熟性高的零部件,以最终形成1个相对低成本,功能性强大、安全性高的产品,特别是在软件行业随着开源和物联网的发展,这些因素结合起来,在自己开发的程序中调用大量的第3方中间件和第3方法软件来集成自己的程序将是今后应用程序主要的开发形式。
3、通过数据交换(赋值)中间件,交换(赋值)数据,是一种特殊形式上的数据清洗操作,只不过前者依赖于特定的中间件,后者依赖于特定的数据库软件。在工程性程序中集成大量的第3方中间件,也是“不要重复的制造轮子”这一基本软件工程指导性原则实际应用的具体体现。
注意:
在基于.Net(Core)框架中通过AutoMapper数据转交(赋值)中间件,自动实现数据交换(赋值)操作,首先必须通过“Nuget”引用:“AutoMapper.Extensions.Microsoft.DependencyInjection”
实体类(程序)与模型类(前台界面(UI))
/// <summary>
/// 【订单--类】
/// <remarks>
/// 摘要:
/// 订单实体类,通过该实体类及其属性成员实现当前程序与数据库中订单表之间的数据交互操作。
/// </remarks>
/// </summary>
public class Order
{
/// <summary>
/// 【编号】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单的整型编号值。
/// </remarks>
/// </summary>
public int Id { get; set; }
/// <summary>
/// 【用户编号】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单相对应用户的整型编号值。
/// </remarks>
/// </summary>
public int CustomId { get; set; }
/// <summary>
/// 【名称】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单的名称。
/// </remarks>
/// </summary>
public string Name { get; set; }
/// <summary>
/// 【订单总价】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单的总价格。
/// </remarks>
/// </summary>
public decimal OrderTotal { get; set; }
/// <summary>
/// 【删除?】
/// <remarks>
/// 摘要:
/// 获取/设置1个值false(不删除)/true(删除活),该值指示1个指定订单是否已经被逻辑删除。
/// </remarks>
public bool Deleted { get; set; }
/// <summary>
/// 【新建日期时间】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单被新建的时间值。
/// </remarks>
/// </summary>
public DateTime CreateTime { get; set; }
}
/// <summary>
/// 【订单项--类】
/// <remarks>
/// 摘要:
/// 订单项实体类,通过该实体类及其属性成员实现当前程序与数据库中订单表项之间的数据交互操作。
/// </remarks>
/// </summary>
public class OrderItem
{
/// <summary>
/// 【项编号】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项的整型编号值。
/// </remarks>
/// </summary>
public int ItemId { get; set; }
/// <summary>
/// 【订单编号】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项相对应订单的整型编号值。
/// </remarks>
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// 【名称】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项的名称。
/// </remarks>
/// </summary>
public string Name { get; set; }
/// <summary>
/// 【数量】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项的数量值。
/// </remarks>
/// </summary>
public decimal Quantity { get; set; }
/// <summary>
/// 【单价】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项的单价。
/// </remarks>
/// </summary>
public decimal UnitPrice { get; set; }
/// <summary>
/// 【新建日期时间】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项被新建的时间值。
/// </remarks>
/// </summary>
public DateTime CreateTime { get; set; }
}
/// <summary>
/// 【订单模型--类】
/// <remarks>
/// 摘要:
/// 通过该类中的属性成员在程序执行时,用于当前程序和前台指定界面(UI)页面之间数据的交互和输入验证操作。
/// </remarks>
/// </summary>
public class OrderDTO
{
/// <summary>
/// 【编号】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单的整型编号值。
/// </remarks>
/// </summary>
public int Id { get; set; }
/// <summary>
/// 【用户编号】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单相对应用户的整型编号值。
/// </remarks>
/// </summary>
public int CustomId { get; set; }
/// <summary>
/// 【订单名称】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单的名称。
/// </remarks>
/// </summary>
public string OrderName { get; set; }
/// <summary>
/// 【订单总计】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单的总价格。
/// </remarks>
/// </summary>
public decimal OrderTotal { get; set; }
/// <summary>
/// 【新建日期时间】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单被新建的时间值。
/// </remarks>
/// </summary>
public DateTime CreateTime { get; set; }
}
/// <summary>
/// 【订单项模型--类】
/// <remarks>
/// 摘要:
/// 通过该类中的属性成员在程序执行时,用于当前程序和前台指定界面(UI)页面之间数据的交互和输入验证操作。
/// </remarks>
/// </summary>
public class OrderItemDTO
{
/// <summary>
/// 【项编号】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项的整型编号值。
/// </remarks>
/// </summary>
public int ItemId { get; set; }
/// <summary>
/// 【订单编号】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项相对应订单的整型编号值。
/// </remarks>
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// 【名称】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项的名称。
/// </remarks>
/// </summary>
public string Name { get; set; }
/// <summary>
/// 【数量】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项的数量值。
/// </remarks>
/// </summary>
public decimal Quantity { get; set; }
/// <summary>
/// 【单价】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项的单价。
/// </remarks>
/// </summary>
public decimal UnitPrice { get; set; }
/// <summary>
/// 【新建日期时间】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定订单项被新建的时间值。
/// </remarks>
/// </summary>
public string CreateTime { get; set; }
}
public static List<OrderItem> GetOrderItemList()
{
OrderItem _orderItem_1 = new OrderItem
{
ItemId = 1,
OrderId = 1,
Name = "Intel 酷睿 i5 12400F",
Quantity = 1,
UnitPrice = 1499.00M,
CreateTime = DateTime.Now,
};
OrderItem _orderItem_2 = new OrderItem
{
ItemId = 2,
OrderId = 1,
Name = "Intel 酷睿 i7 12700KF",
Quantity = 2,
UnitPrice = 2949.00M,
CreateTime = DateTime.Now,
};
List<OrderItem> _orderItemList = new List<OrderItem> { _orderItem_1, _orderItem_2 };
return _orderItemList;
}
public static Order GetOrder(List<OrderItem> orderItemList)
{
decimal _orderTotal = 0;
foreach(var item in orderItemList)
{
_orderTotal += item.Quantity * item.UnitPrice;
}
Order _order_1 = new Order
{
Id = 1,
CustomId = 1,
Name = "测试",
OrderTotal = _orderTotal,
Deleted = false,
CreateTime = DateTime.Now,
};
return _order_1;
}
通过MapperConfiguration配置AutoMapper
通过MapperConfiguration泛型配置实现数据交换(赋值)操作
/// <summary>
/// 【映射扩展--类】
/// <remarks>
/// 摘要:
/// 该类通过“AutoMapper”中间件,把1个指定类型的1/多个指定实例中的数据直接赋值到另1个指定类型的1/多个指定实例中。
/// 注意:
/// 该类及其所有成员都被限定为静态。
/// </remarks>
/// </summary>
public static class MapExtensions
{
/// <typeparam name="TSource">泛型类型实例(1个指定的要被转化类的类型实例)。</typeparam>
/// <typeparam name="TDestination">泛型类型实例(这里主要指,1个指定的转化后类的类型实例)。</typeparam>
/// <param name="source">1个指定的要被转化的实体的1个实例。</param>
/// <summary>
/// 【映射到】
/// <remarks>
/// 摘要:
/// 通过相应的参数实例及其“AutoMapper”中间件,把1个指定类型的1个指定实例中的数据直接赋值到另1个指定类型的1个指定实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 被赋值后的另1个指定类型的1个指定实例。
/// </returns>
/// </summary>
public static TDestination MapTo<TSource, TDestination>(this TSource source) where TSource : class where TDestination : class
{
if (source == null) return default(TDestination);
var config = new MapperConfiguration(cfg => cfg.CreateMap<TSource, TDestination>());
var mapper = config.CreateMapper();
return mapper.Map<TDestination>(source);
}
/// <typeparam name="TSource">泛型类型实例(1个指定的要被转化类的类型实例)。</typeparam>
/// <typeparam name="TDestination">泛型类型实例(这里主要指,1个指定的转化后类的类型实例)。</typeparam>
/// <param name="source">枚举数实例,该实例中存储着1个指定的要被转化的实体的多个实例。</param>
/// <summary>
/// 【映射到列表】
/// <remarks>
/// 摘要:
/// 通过相应的参数实例及其“AutoMapper”中间件,把1个指定类型的多个指定实例中的数据直接赋值到另1个指定类型的多个指定实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 枚举数实例,该实例中存储着1个指定的要被转化的实体的多个实例。
/// </returns>
/// </summary>
public static IEnumerable<TDestination> MapToList<TSource, TDestination>(this IEnumerable<TSource> source) where TSource : class where TDestination : class
{
if (source == null) return new List<TDestination>();
var config = new MapperConfiguration(cfg => cfg.CreateMap<TSource, TDestination>());
var mapper = config.CreateMapper();
return mapper.Map<List<TDestination>>(source);
}
}
public IActionResult ExtensionsConfiguration()
{
OrderDTO _orderDTO = GetOrder(GetOrderItemList()).MapTo<Order,OrderDTO>();
return View(_orderDTO);
}
public IActionResult ExtensionsConfigurationList()
{
return View(GetOrderItemList().MapToList<OrderItem,OrderItemDTO>());
}
注意:
由于数据转换(赋值)操作主要针对不完全相同的数据之间进行的(数据清洗),所以通过泛型操作来实现数据转换(赋值)操作,在实际实现中并不实用;在实际应用中最为实用的还是通过定制配置操作,来实现数据转换(赋值)操作。
如上图所示由于两个不同类中的属性成员的名称不完全相同,如果使用泛型的通用映射规则的配置,就会因数据转换操作不能进行相互映射,而导至“订单名称”无与之相对应的数据。
通过MapperConfiguration定制配置实现数据交换(赋值)操作
/// <summary>
/// 【映射定制--类】
/// <remarks>
/// 摘要:
/// 该类通过“AutoMapper”中间件,把订单实体的1/多个实例中的数据直接赋值到订单模型类的1/多个指定实例中。
/// 注意:
/// 该类及其所有成员都被限定为静态。
/// </remarks>
/// </summary>
public static class MapCustomized
{
/// <param name="order">订单实体的1个实例。</param>
/// <summary>
/// 【订单定制】
/// <remarks>
/// 摘要:
/// 通过相应的参数实例及其“AutoMapper”中间件,把订单实体的1个实例中的数据直接赋值到订单模型类的1个指定实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 被赋值后的订单模型类的1个指定实例。
/// </returns>
/// </summary>
public static OrderDTO OrderCustomized(this Order order)
{
if (order == null) return default(OrderDTO);
var config = new MapperConfiguration(cfg => cfg.CreateMap<Order, OrderDTO>()
.ForMember(d => d.OrderName, o => o.MapFrom(o => o.Name)));//字段名称不一致将“Name”映射到“OrderName”。
IMapper _mapper = config.CreateMapper();
return _mapper.Map<OrderDTO>(order);
}
/// <param name="orderItemList">列表实例,该实例中存储着订单实体的多个实例。</param>
/// <summary>
/// 【订单项定制】
/// <remarks>
/// 摘要:
/// 通过相应的参数实例及其“AutoMapper”中间件,把订单实体的多个实例中的数据直接赋值到订单模型类的多个指定实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 列表实例,该实例中存储着订单实体的多个实例。
/// </returns>
/// </summary>
public static List<OrderItemDTO> OrderItemCustomized(this List<OrderItem> orderItemList)
{
if (orderItemList == null) return new List<OrderItemDTO>();
var config = new MapperConfiguration(cfg => cfg.CreateMap<OrderItem, OrderItemDTO>()
.ForMember(d=>d.CreateTime, options => options.MapFrom(o => o.CreateTime.ToString("yyyyMMddHHmmssfff")))); //字段类型不一致将“DateTime”类型的数据映射并格式化为“String”类型的数据。
IMapper _mapper = config.CreateMapper();
return _mapper.Map<List<OrderItemDTO>>(orderItemList);
}
} public IActionResult CustomizedConfiguration()
{
OrderDTO _orderDTO = GetOrder(GetOrderItemList()).OrderCustomized();
return View(_orderDTO);
}
public IActionResult CustomizedConfigurationList()
{
return View(GetOrderItemList().OrderItemCustomized());
}
通过继承Profile定制配置AutoMapper
/// <summary>
/// 【订单映射配置--类】
/// <remarks>
/// 摘要:
/// 通过当前类所定义的定制映射规则,在订单类和订单项类与订单模型类和订单项型类之间的属性成员直接通过“AutoMapper”中间件,进行数据相互赋值操作时;
/// 如果两者的属性成员之间有差异时,使两者属性成员之间的数据相互赋值操作,得以正常的被执行。
/// </remarks>
/// </summary>
public class OrderMapperProfile : Profile
{
public OrderMapperProfile()
{
//属性成员名称不一致将“Name”映射到“OrderName”。
CreateMap<Order, OrderDTO>().ForMember(dest => dest.OrderName, src => src.MapFrom(s => s.Name));
//属性成员类型不一致将“DateTime”类型的数据映射并格式化为“String”类型的数据。
CreateMap<OrderItem, OrderItemDTO>().ForMember(dest => dest.CreateTime, src => src.ConvertUsing(new FormatConvert()));
//全局映射:在“Order”映射到“OrderDTO”时,忽略“Order”中的“Deleted”属性成员。
((IProfileExpressionInternal)this).ForAllMaps((mapConfiguration, map) =>
{
if (typeof(Order).IsAssignableFrom(mapConfiguration.DestinationType))
{
map.ForMember(nameof(Order.Deleted), options => options.Ignore());
}
});
}
}
/// <summary>
/// 【格式转换--类】
/// <remarks>
/// 摘要:
/// 如果两个类之间的属性成员直接通过“AutoMapper”中间件,进行数据相互赋值操作时;如果两者的属性成员之间数据类型不同且需要进行数据相互赋值操作时,
/// 通过当前类中所定义的方法成员,使两个不同数据类型的属性成员之间,正常执行数据相互赋值操作。
/// </remarks>
/// </summary>
public class FormatConvert : IValueConverter<DateTime, string>
{
/// <param name="sourceMember">1个将要被转换属性成员的时间类型的值。</param>
/// <param name="context">解决方案上下文实例。</param>
/// <summary>
/// 【转换】
/// <remarks>
/// 摘要:
/// 如果两个类之间的属性成员直接通过“AutoMapper”中间件,进行数据相互赋值操作时;如果两者的属性成员之间数据类型不同且需要进行数据相互赋值操作时,
/// 通过该方法成员,使两个不同数据类型的属性成员之间,正常执行数据相互赋值操作。
/// </remarks>
/// <returns>
/// 返回:
/// 被转换后的字符串类型的数据值。
/// </returns>
/// </summary>
public string Convert(DateTime sourceMember, ResolutionContext context)
{
if (sourceMember == DateTime.MinValue)
return DateTime.Now.ToString("yyyyMMddHHmmssfff");
return sourceMember.ToString("yyyyMMddHHmmssfff");
}
}
注意:
在基于.Net(Core)框架的程序中使用继承于Profile的类来定义定制的数据交换规则,必须先把该类(或该类所定义的程序集)依赖注入到内置依赖注入容器中,开发者可以选择其中的任意1种进行注入:
//通过数据交换映射规则配置文件类,直接注入。
builder.Services.AddAutoMapper(typeof(OrderMapperProfile));
//通过当前程序作用域中的所有程序集里面扫描AutoMapper的配置文件“OrderMapperProfile”,注入。
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
// 通过数据交换映射规则配置文件类“OrderMapperProfile”所定义的程序集的名称,注入
builder.Services.AddAutoMapper(Assembly.Load("AutoMapperWeb"));
private readonly IMapper _mapper;
public HomeController(IMapper mapper)
{
_mapper = mapper;
}
如果不注入直接执行交换操作就会出现逻辑异常:“InvalidOperationException: Unable to resolve service for type 'AutoMapper.IMapper' while attempting to activate 'AutoMapperWeb.Controllers.HomeController'.”
public IActionResult CustomizedProfile()
{
OrderDTO _orderDTO = _mapper.Map<OrderDTO>(GetOrder(GetOrderItemList()));
return View(_orderDTO);
}
public IActionResult CustomizedProfileList()
{
return View(_mapper.Map<List<OrderItemDTO>>(GetOrderItemList()));
}
对以上功能更为具体实现和注释见:22-05-19-049_AutoMapperWeb(AutoMapper深入理解)。