如何使用 AutoMapper 将一些源属性映射到包装的目标类型?
How to map some source properties to a wrapped destination type using AutoMapper?
假设你有这个源模型:
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 默认情况下(根据正常的 AutoMapper 配置),但是从 SourceModelBase
派生的每个东西都应该被映射到包装器 class 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; }
}
因为联系人 class 本身是从 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
,则以某种方式应用自定义逻辑以根据源 [=42= 的值提供 Wrap<T>
值].
不过我完全不知道从哪里开始。特别是当源对象本身也被指定为包装逻辑的主题时。
什么是最好、最符合 AutoMapper 惯用的方式来包装嵌套对象(如果它们满足条件)并同时包装原始对象(如果它满足相同条件)? 我已经抽象了映射器的创建,所以我可以在将原始对象传递给映射器之前自动塑造原始对象,这可能有助于通过执行 mapper.Map(new { Root = originalObject })
将原始对象置于解析器之下,以便解析器看到原始对象的实例,就好像它也是源对象的 属性 的值,而不是源对象本身。
根据 AutoMapper GitHub 页面上的 this issue,没有直接的方法。
但是有一些解决方法。例如 - 反射。
在这种情况下,您需要了解包装器类型并为所需类型实现转换器。在这个例子中它是 MapAndWrapConverter
从 TSource
到 Wrap<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
方法有点乱,尤其是查找匹配类型的部分。但是可以根据自己的需要细化。
假设你有这个源模型:
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 默认情况下(根据正常的 AutoMapper 配置),但是从 SourceModelBase
派生的每个东西都应该被映射到包装器 class 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; }
}
因为联系人 class 本身是从 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
,则以某种方式应用自定义逻辑以根据源 [=42= 的值提供 Wrap<T>
值].
不过我完全不知道从哪里开始。特别是当源对象本身也被指定为包装逻辑的主题时。
什么是最好、最符合 AutoMapper 惯用的方式来包装嵌套对象(如果它们满足条件)并同时包装原始对象(如果它满足相同条件)? 我已经抽象了映射器的创建,所以我可以在将原始对象传递给映射器之前自动塑造原始对象,这可能有助于通过执行 mapper.Map(new { Root = originalObject })
将原始对象置于解析器之下,以便解析器看到原始对象的实例,就好像它也是源对象的 属性 的值,而不是源对象本身。
根据 AutoMapper GitHub 页面上的 this issue,没有直接的方法。
但是有一些解决方法。例如 - 反射。
在这种情况下,您需要了解包装器类型并为所需类型实现转换器。在这个例子中它是 MapAndWrapConverter
从 TSource
到 Wrap<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
方法有点乱,尤其是查找匹配类型的部分。但是可以根据自己的需要细化。