如何使用自动映射程序将某些源属性映射到包装的目标类型

本文关键字:映射 属性 包装 类型 目标 何使用 映射程序 | 更新日期: 2023-09-27 17:56:42

假设你有这个源模型:

public abstract class SourceModelBase {
}
public class SourceContact : SourceModelBase {
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public KeyValuePair Pair { get; set; }
  public SourceAddress Address { get; set; }
}
public class KeyValuePair { // Not derived from SourceModelBase.
  public string Key { get; set; }
  public string Value { get; set; }
}
public class SourceAddress : SourceModelBase {
  public string StreetName { get; set; }
  public string StreetNumber { get; set; }
}

现在,目标模型应该默认为 1:1 映射(受制于正常的自动映射器配置),但从SourceModelBase派生的每个东西都应该映射到包装类class Wrap<T> { T Payload { get; set; } string Meta { get; set; } }

public abstract class DestinationModelBase {
}
public class DestinationContact : DestinationModelBase {
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public KeyValuePair Pair { get; set; } // Not wrapped, base class not `SourceModelBase`.
  public Wrap<DestinationAddress> Address { get; set; }
}
public class DestinationAddress : DestinationModelBase {
  public string StreetName { get; set; }
  public string StreetNumber { get; set; }
}

由于 contact 类本身派生自 SourceModelBase因此也应该包装它。

结果应具有以下结构:

Wrap<DestinationContact> Contact
  string Meta // Comes from the custom wrapper logic.
  DestinationContact Payload
    string FirstName
    string LastName
    KeyValuePair Pair
      string Key
      string Value
    Wrap<DestinationAddress> Address
      string Meta // Comes from the custom wrapper logic.
      DestinationAddress Payload
        string StreetName
        string StreetNumber

显然,这种包装应该嵌套,映射对象本身受制于它,其Address属性也是如此。

出于某种原因,我一直发现的都是与从目的地到源的映射相关的问题。我知道我必须以某种方式使用ResolveUsing,如果目标类型派生自SourceModelBase,则以某种方式应用自定义逻辑以根据源属性的值提供Wrap<T>值。

不过,我根本不知道从哪里开始。特别是当源对象本身也被指定为包装逻辑的主题时。

如果嵌套对象满足条件,同时包装原始对象,

如果它满足相同的条件,那么包装原始对象的最佳、最惯用的方法是什么?我已经抽象了映射器的创建,因此我可以在将原始对象传递给映射器之前自动塑造原始对象,这也可能有助于通过执行mapper.Map(new { Root = originalObject })将原始对象置于解析器之下,以便解析器看到原始对象的实例,就好像它是源对象的属性值一样, 而不是源对象本身。

如何使用自动映射程序将某些源属性映射到包装的目标类型

根据AutoMapper GitHub页面上的此问题,没有直接的方法可以做到这一点。
但是有一些解决方法。例如 - 反射。
在这种情况下,您需要知道包装器类型并为所需类型实现转换器。在此示例中,它是从TSource MapAndWrapConverterWrap<TDestination>
CreateWrapMap方法创建两个绑定:
SourceAddress -> Wrap<DestinationAddress>SourceContact -> Wrap<DestinationContact>,允许您将SourceContant映射到包装DestinationContact

internal class Program
{
    public static void Main()
    {
        var config = new MapperConfiguration(cfg =>
        {
            cfg.CreateMap<SourceAddress, DestinationAddress>();
            cfg.CreateMap<SourceContact, DestinationContact>();
            cfg.CreateWrapMap(
                //func selecting types to wrap
                type => typeof(DestinationModelBase).IsAssignableFrom(type)
                        && !type.IsAbstract,
                typeof(Wrap<>),
                typeof(MapAndWrapConverter<,>));
        });
        var mapper = config.CreateMapper();
        //Using AutoFixture to create complex object
        var fixture = new Fixture();
        var srcObj = fixture.Create<SourceContact>();
        var dstObj = mapper.Map<Wrap<DestinationContact>>(srcObj);
    }
}
public static class AutoMapperEx
{
    public static IMapperConfigurationExpression CreateWrapMap(
        this IMapperConfigurationExpression cfg,
        Func<Type, bool> needWrap, Type wrapperGenericType,
        Type converterGenericType)
    {
        var mapperConfiguration = 
            new MapperConfiguration((MapperConfigurationExpression)cfg);
        var types = Assembly.GetExecutingAssembly().GetTypes();
        foreach (var dstType in types.Where(needWrap))
        {
            var srcType = mapperConfiguration.GetAllTypeMaps()
                .Single(map => map.DestinationType == dstType).SourceType;
            var wrapperDstType = wrapperGenericType.MakeGenericType(dstType);
            var converterType = converterGenericType.MakeGenericType(srcType, dstType);
            cfg.CreateMap(srcType, wrapperDstType)
                .ConvertUsing(converterType);
        }
        return cfg;
    }
}
public class MapAndWrapConverter<TSource, TDestination> 
    : ITypeConverter<TSource, Wrap<TDestination>>
{
    public Wrap<TDestination> Convert(
        TSource source, Wrap<TDestination> destination, ResolutionContext context)
    {
        return new Wrap<TDestination>
        {
            Payload = context.Mapper.Map<TDestination>(source)
        };
    }
}

CreateWrapMap方法有点混乱,尤其是查找匹配类型的部分。但它可以根据您的需要进行改进。