我一直在将域对象展平为DTO,如下例所示:
public class Root
{
public string AParentProperty { get; set; }
public Nested TheNestedClass { get; set; }
}
public class Nested
{
public string ANestedProperty { get; set; }
}
public class Flattened
{
public string AParentProperty { get; set; }
public string ANestedProperty { get; set; }
}
// I put the equivalent of the following in a profile, configured at application start
// as suggested by others:
Mapper.CreateMap<Root, Flattened>()
.ForMember
(
dest => dest.ANestedProperty
, opt => opt.MapFrom(src => src.TheNestedClass.ANestedProperty)
);
// This is in my controller:
Flattened myFlattened = Mapper.Map<Root, Flattened>(myRoot);
我已经看了很多例子,到目前为止,这似乎是扁平嵌套层次结构的方法。但是,如果子对象具有许多属性,则此方法不会节省太多编码。
我找到了这个例子:
但是它需要Map()函数所需的映射对象的实例,这对我理解的配置文件不起作用。
我是AutoMapper的新手,所以我想知道是否有更好的方法来做到这一点。
答案 0 :(得分:14)
在最新版本的AutoMapper中,您可以使用一种命名约定来避免多个.ForMember语句。
在您的示例中,如果您将Flattened类更新为:
public class Flattened
{
public string AParentProperty { get; set; }
public string TheNestedClassANestedProperty { get; set; }
}
您可以避免使用ForMember语句:
Mapper.CreateMap<Root, Flattened>();
在这种情况下,自动映射器(按惯例)将Root.TheNestedClass.ANestedProperty
映射到Flattened.TheNestedClassANestedProperty
。当你使用真正的班级名字时,它看起来不那么难看,诚实!
答案 1 :(得分:5)
另外两种解决方案:
Mapper.CreateMap<Nested, Flattened>()
.ForMember(s=>s.AParentProperty, o=>o.Ignore());
Mapper.CreateMap<Root, Flattened>()
.ForMember(d => d.ANestedProperty, o => o.MapFrom(s => s.TheNestedClass));
另一种方法如下,但它不会通过Mapper.AssertConfigurationIsValid()
。
Mapper.CreateMap<Nested, Flattened>()
//.ForMember map your properties here
Mapper.CreateMap<Root, Flattened>()
//.ForMember... map you properties here
.AfterMap((s, d) => Mapper.Map(s.TheNestedClass, d));
答案 2 :(得分:3)
不确定这是否会为以前的解决方案增加价值,但您可以将其作为两步映射来实现。如果父级和子级之间存在命名冲突(最后的胜利),请小心按正确的顺序进行映射。
Mapper.CreateMap<Root, Flattened>();
Mapper.CreateMap<Nested, Flattened>();
var flattened = new Flattened();
Mapper.Map(root, flattened);
Mapper.Map(root.TheNestedClass, flattened);
答案 3 :(得分:1)
我写了扩展方法来解决类似的问题:
public static IMappingExpression<TSource, TDestination> FlattenNested<TSource, TNestedSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression,
Expression<Func<TSource, TNestedSource>> nestedSelector,
IMappingExpression<TNestedSource, TDestination> nestedMappingExpression)
{
var dstProperties = typeof(TDestination).GetProperties().Select(p => p.Name);
var flattenedMappings = nestedMappingExpression.TypeMap.GetPropertyMaps()
.Where(pm => pm.IsMapped() && !pm.IsIgnored())
.ToDictionary(pm => pm.DestinationProperty.Name,
pm => Expression.Lambda(
Expression.MakeMemberAccess(nestedSelector.Body, pm.SourceMember),
nestedSelector.Parameters[0]));
foreach (var property in dstProperties)
{
if (!flattenedMappings.ContainsKey(property))
continue;
expression.ForMember(property, opt => opt.MapFrom((dynamic)flattenedMappings[property]));
}
return expression;
}
所以在你的情况下它可以像这样使用:
var nestedMap = Mapper.CreateMap<Nested, Flattened>()
.IgnoreAllNonExisting();
Mapper.CreateMap<Root, Flattened>()
.FlattenNested(s => s.TheNestedClass, nestedMap);
IgnoreAllNonExisting()
来自here。
虽然它不是通用的解决方案,但对于简单的案例来说应该足够了。
所以,
答案 4 :(得分:1)
要改进另一个答案,请为两个映射指定MemberList.Source
并设置要忽略的嵌套属性。验证然后通过确定。
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SrcNested, DestFlat>(MemberList.Source);
cfg.CreateMap<SrcRoot, DestFlat>(MemberList.Source)
.ForSourceMember(s => s.Nested, x => x.Ignore())
.AfterMap((s, d) => Mapper.Map(s.Nested, d));
});
Mapper.AssertConfigurationIsValid();
var dest = Mapper.Map<SrcRoot, DestFlat>(src);
答案 5 :(得分:1)
我更喜欢避免使用旧的Static方法,而是这样做。
将映射定义放入个人资料。我们首先映射根,然后再应用嵌套的映射。请注意 Context 的使用。
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Root, Flattened>()
.AfterMap((src, dest, context) => context.Mapper.Map(src.TheNestedClass, dest));
CreateMap<Nested, Flattened>();
}
}
同时定义从 Root 到 Flattened 和 Nested 到 Flatterned 的映射的优点是您保留了完全控制属性的映射,例如目标属性名称是否不同或您要应用转换等。
XUnit测试:
[Fact]
public void Mapping_root_to_flattened_should_include_nested_properties()
{
// ARRANGE
var myRoot = new Root
{
AParentProperty = "my AParentProperty",
TheNestedClass = new Nested
{
ANestedProperty = "my ANestedProperty"
}
};
// Manually create the mapper using the Profile
var mapper = new MapperConfiguration(cfg => cfg.AddProfile(new MappingProfile())).CreateMapper();
// ACT
var myFlattened = mapper.Map<Root, Flattened>(myRoot);
// ASSERT
Assert.Equal(myRoot.AParentProperty, myFlattened.AParentProperty);
Assert.Equal(myRoot.TheNestedClass.ANestedProperty, myFlattened.ANestedProperty);
}
通过从 AutoMapper.Extensions.Microsoft.DependencyInjection nuget包中添加AutoMapper的 serviceCollection.AddAutoMapper()到您的启动程序中,配置文件将被自动提取,并且您只需将 IMapper 注入到要应用映射的任何位置即可。
答案 6 :(得分:0)
我使用AutoMappers新的命名约定规则创建了一个简单的示例,以从展平的对象映射到嵌套的对象,希望对您有帮助