65.9K
CodeProject 正在变化。 阅读更多。
Home

通过 IMappingOperationOptions 实现 AutoMapper 运行时映射控制

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (3投票s)

2021年12月21日

CPOL

3分钟阅读

viewsIcon

9683

简要了解 AutoMapper 操作选项的可能性

引言

AutoMapper 是一个在两个类之间轻松映射的绝佳工具。 但是,如果映射发生在例如 NHibernate 实体和 DTO(数据传输对象)之间,它可能会变得危险。 借助 NHibernate 的延迟加载功能,这很容易导致不必要的数据库选择。 AutoMapper 具有一些不错的功能,可以通过 Queryable Extensions,让开发人员在运行时控制它应该映射什么和不应该映射什么。

但还有一个相当被低估的功能,隐藏在其 ResolutionContext -> Options 中,文档中只是简要提及:将键值传递给 Mapper

我说的是名为 "Items" 的小巧属性,它只是一个 IDictionary<string, object>

有了这个,您可以直接控制从数据库接收的数据,我将用一个小例子向您展示。

背景

一方面有带有引用和列表的实体,另一方面有 DTO,您可能会发现 AutoMapper 总是会尝试映射它可以映射的所有内容。 因此,您要么必须为不同的用例创建不同的 DTO,要么只需使用 NHibernate 的延迟加载的魔力,并通过前端可以直接传递到远程调用的标志进行直接控制。

让我们看看它是如何工作的!

Using the Code

考虑一下这两个类的简单示例:CustomerOrders
(这只是一个基本示例,不要与实际公司应用程序混淆。)

一方面,您有这些 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

如果设置为 falseAutoMapper 将不会映射 Orders,并且 DTO 内的 Order 列表将保持为空。 NHibernate 将不会延迟加载 Orders

当然,这可以使用任何其他属性来完成,并且将任何内容存储在 String-ObjectDictionary 中的可能性是无限的。 在这种情况下,它们只是为了更好地控制应该映射什么的标志。

结论

使用 AutoMapper 的操作选项可以让你直接控制从前端到后端的运行时映射,从而减少类和缩短代码。

注意

我知道你也可以简单地通过代码检查 includeOrders,为 Orders 执行另一个查询并手动填充它们。 但这只是一个提高意识的基本示例。 我相信其他开发人员可以使用 Dictionary 来控制和操作 AutoMapper 的行为。

关注点

老实说,当我第一次偶然发现这个简单的 Dictionary 时,它让我震惊于它真正的强大之处。
我首先想到的是这个扩展,以便更好地控制 AutoMapper 的行为,而无需维护太多不同的 DTO。 我希望这将使其他不喜欢或改用其他映射器的开发者重新关注 AutoMapper

控制一切!

历史

  • 2021 年 12 月 21 日:初始版本
© . All rights reserved.