通过 IMappingOperationOptions 实现 AutoMapper 运行时映射控制
简要了解 AutoMapper 操作选项的可能性
引言
AutoMapper 是一个在两个类之间轻松映射的绝佳工具。 但是,如果映射发生在例如 NHibernate 实体和 DTO(数据传输对象)之间,它可能会变得危险。 借助 NHibernate 的延迟加载功能,这很容易导致不必要的数据库选择。 AutoMapper 具有一些不错的功能,可以通过 Queryable Extensions,让开发人员在运行时控制它应该映射什么和不应该映射什么。
但还有一个相当被低估的功能,隐藏在其 ResolutionContext
-> Options
中,文档中只是简要提及:将键值传递给 Mapper。
我说的是名为 "Items
" 的小巧属性,它只是一个 IDictionary<string, object>
。
有了这个,您可以直接控制从数据库接收的数据,我将用一个小例子向您展示。
背景
一方面有带有引用和列表的实体,另一方面有 DTO,您可能会发现 AutoMapper 总是会尝试映射它可以映射的所有内容。 因此,您要么必须为不同的用例创建不同的 DTO,要么只需使用 NHibernate 的延迟加载的魔力,并通过前端可以直接传递到远程调用的标志进行直接控制。
让我们看看它是如何工作的!
Using the Code
考虑一下这两个类的简单示例:Customer
和 Orders
。
(这只是一个基本示例,不要与实际公司应用程序混淆。)
一方面,您有这些 NHibernate 实体
public class Customer
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Order> Orders { get; set; }
}
public class Order
{
public virtual long Id { get; set; }
public virtual string ProductNumber { get; set; }
public virtual int Amount { get; set; }
}
这里是 DTO
public class CustomerDto
{
public long Id { get; set; }
public string Name { get; set; }
public IList<OrderDto> Orders { get; set; }
}
public class OrderDto
{
public long Id { get; set; }
public string ProductNumber { get; set; }
public int Amount { get; set; }
}
现在,假设您有两个用例
- 仅检索用户
- 检索包含其订单的用户
为此,我们需要为 IMappingOperationOptions
创建一个扩展类。
此扩展将从 Options
-> Items
存储和检索标志。
public static class OperationOptionExtensions
{
// List of different keys for the Items Dictionary
private const string ShouldIncludeOrdersForCustomerKey = "ShouldIncludeOrdersForCustomer";
// ----
/// <summary>
/// Retreives a bool value from the Items Dictionary by key
/// </summary>
private static bool GetBoolValue(IMappingOperationOptions options, string key)
{
if (options.Items.ContainsKey(key) && options.Items[key] is bool value)
{
return value;
}
return false;
}
/// <summary>
/// Saves the bool value, whenever or not the mapping should include
/// the Orders List for Customer
/// </summary>
public static void IncludeOrdersForCustomer(
this IMappingOperationOptions options,
bool include = true)
{
options.Items[ShouldIncludeOrdersForCustomerKey] = include;
}
/// <summary>
/// Mapping in Profile requests, whenever or not it should include
/// the Orders List for Customer
/// </summary>
public static bool ShouldIncludeOrdersForCustomer(this IMappingOperationOptions options)
{
return GetBoolValue(options, ShouldIncludeOrdersForCustomerKey);
}
}
现在我们可以配置我们的映射配置文件,告诉 AutoMapper
仅在使用 PreCondition
操作设置标志时才映射 Orders
public class AutoMapperConfig : Profile
{
public AutoMapperConfig()
{
CreateMap<Customer, CustomerDto>()
// The PreCondition retreives the stored value
// from the Items Dictionary inside the mapping options
.ForMember(
customer => customer.Orders,
config => config.PreCondition(
context => context.Options.ShouldIncludeOrdersForCustomer()));
CreateMap<Order, OrderDto>();
}
}
默认情况下,该条件返回 false
,因此只有在运行时启用该选项时才会映射 Order
。
考虑一下使用 NHibernate 的数据库访问层的简化示例
public class DBAccess
{
// Assume Dependency-Injection of NHibernate Session here
public ISession Session { get; set; }
private IMapper Mapper { get; }
public DBAccess()
{
// Profile and configuration
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<AutoMapperConfig>();
});
// Also typically by Dependency-Injection
Mapper = config.CreateMapper();
}
/// <summary>
/// Data-Access method: includeOrders will be requested by the front-end
/// </summary>
public CustomerDto GetCustomerById(long customerId, bool includeOrders)
{
// Retreive the Customer from database
var customer = Session.Get<Customer>(customerId);
// We directly inject the clients request for the Orders
// into this mapping operation option
return Mapper.Map<CustomerDto>(customer, options =>
{
options.IncludeOrdersForCustomer(includeOrders);
});
}
}
如果 includeOrders
设置为 true
,则 AutoMapper
将通过触发 NHibernate 的延迟加载来映射 Orders
。
如果设置为 false
,AutoMapper
将不会映射 Orders
,并且 DTO 内的 Order
列表将保持为空。 NHibernate 将不会延迟加载 Orders
。
当然,这可以使用任何其他属性来完成,并且将任何内容存储在 String
-Object
的 Dictionary
中的可能性是无限的。 在这种情况下,它们只是为了更好地控制应该映射什么的标志。
结论
使用 AutoMapper 的操作选项可以让你直接控制从前端到后端的运行时映射,从而减少类和缩短代码。
注意
我知道你也可以简单地通过代码检查 includeOrders
,为 Orders
执行另一个查询并手动填充它们。 但这只是一个提高意识的基本示例。 我相信其他开发人员可以使用 Dictionary
来控制和操作 AutoMapper
的行为。
关注点
老实说,当我第一次偶然发现这个简单的 Dictionary
时,它让我震惊于它真正的强大之处。
我首先想到的是这个扩展,以便更好地控制 AutoMapper 的行为,而无需维护太多不同的 DTO。 我希望这将使其他不喜欢或改用其他映射器的开发者重新关注 AutoMapper
。
控制一切!
历史
- 2021 年 12 月 21 日:初始版本