我使用的是AutoMapper 6.2.2,我有两个共享Id
属性的源模型:
using System.Diagnostics;
using AutoMapper;
public class Outer
{
public int Id { get; set; }
public string Foo { get; set; }
public Inner Bar { get; set; }
}
public class Inner
{
public int Id { get; set; }
public string Baz { get; set; }
public string Qux { get; set; }
public string Bof { get; set; }
}
public class FlatDto
{
public int Id { get; set; }
public string Foo { get; set; }
public string Baz { get; set; }
public string Qux { get; set; }
public string Bof { get; set; }
}
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
this.CreateMap<Outer, FlatDto>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(s => s.Id))
.ForMember(dst => dst.Foo, opt => opt.MapFrom(s => s.Foo))
.ForMember(dst => dst.Baz, opt => opt.MapFrom(s => s.Bar.Baz))
.ForMember(dst => dst.Qux, opt => opt.MapFrom(s => s.Bar.Qux))
.ForMember(dst => dst.Bof, opt => opt.MapFrom(s => s.Bar.Bof));
}
}
class Program
{
static void Main(string[] args)
{
Outer model = new Outer
{
Id = 1,
Foo = "FooString",
Bar = new Inner
{
Id = 2,
Baz = "BazString",
Qux = "QuxString",
Bof = "BofString"
}
};
var config = new MapperConfiguration(cfg => cfg.AddProfiles(typeof(Program).Assembly));
config.AssertConfigurationIsValid();
IMapper mapper = new Mapper(config);
FlatDto dto = mapper.Map<Outer, FlatDto>(model);
Trace.Assert(model.Id == dto.Id);
Trace.Assert(model.Foo == dto.Foo);
Trace.Assert(model.Bar.Baz == dto.Baz);
Trace.Assert(model.Bar.Qux == dto.Qux);
Trace.Assert(model.Bar.Bof == dto.Bof);
}
}
我希望FlatDto.Id
来自Outer
,其他参数全部来自名称。在这种情况下,AutoMapper的惯例非常清楚,但我无法修改这些属性。它目前针对每个dest属性显式映射ForMember
。类似question的解决方案实际上甚至更长。
对于这种情况,是否存在更优雅的解决方案,其中两个模型都包含多个字段且只有一个重叠且需要显式处理?
答案 0 :(得分:-1)
最简单的解决方案(即使您将来修改了Outer / Inner也没有更改代码)是:
Mapper.Initialize(c =>
{
c.CreateMap<Inner, FlatDto>();
c.CreateMap<Outer, FlatDto>().BeforeMap((s, t) => Mapper.Map(s.Bar, t));
});
请注意:
如果您使用实例映射器而不是静态.Map
方法&#34; global&#34;则需要更改映射器。
Inner
和Outer
中具有相同名称的属性将被映射两次,Outer
具有更高的优先级,请注意可能的副作用。
编辑由于您使用的是实例映射器和配置文件,因此无法在配置文件中访问实例IMapper
,我们需要注册动态映射。以下代码,就像问题中的代码片段一样,基本上使用.ForMember
,并在动态表达式中构建参数。
class TestProfile : Profile
{
public TestProfile()
{
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
Func<PropertyInfo, bool> filter = p => p.CanRead && p.CanWrite;
var outerProperties = typeof(Outer).GetProperties(flags).Where(filter).ToDictionary(p => p.Name);
var innerProperties = typeof(Inner).GetProperties(flags).Where(filter);
var mappingProperties = innerProperties.Where(p => !outerProperties.ContainsKey(p.Name));
//code above gets the properties of Inner that needs to be mapped
var outerParameter = Expression.Parameter(typeof(Outer));
var accessBar = Expression.Property(outerParameter, nameof(Outer.Bar));
var map = CreateMap<Outer, FlatDto>();
var mapExp = Expression.Constant(map);
foreach (var property in mappingProperties)
{
var accessProperty = Expression.MakeMemberAccess(accessBar, property);
var funcType = typeof(Func<,>).MakeGenericType(typeof(Outer), property.PropertyType);
var funcExp = Expression.Lambda(funcType, accessProperty, outerParameter);
//above code builds s => s.Bar.Qux
var configType = typeof(IMemberConfigurationExpression<,,>).MakeGenericType(typeof(Outer), typeof(FlatDto), typeof(object));
var configParameter = Expression.Parameter(configType);
var mapFromMethod = configType
.GetMethods()
.Single(m => m.Name == "MapFrom" && m.IsGenericMethod)
.MakeGenericMethod(property.PropertyType);
var invokeMapFrom = Expression.Call(configParameter, mapFromMethod, funcExp);
var configExp = Expression.Lambda(typeof(Action<>).MakeGenericType(configType), invokeMapFrom, configParameter);
//above code builds opt => opt.MapFrom(s => s.Bar.Qux)
var forMemberMethod = map.GetType()
.GetMethods()
.Single(m => m.Name == "ForMember" && !m.IsGenericMethod);
var invokeForMember = Expression.Call(mapExp, forMemberMethod, Expression.Constant(property.Name), configExp);
//above code builds map.ForMember("Qux", opt => opt.MapFrom(s => s.Bar.Qux))
var configAction = Expression.Lambda<Action>(invokeForMember);
configAction.Compile().Invoke();
}
}
}
看起来非常庞大的代码,但事实上你可以(并且应该)将get属性/方法片段放在其他地方,foreach循环本身使用它们来构建一个表达式来调用。它非常干净有效。