26

我一直在将域对象扁平化为 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);

我看过一些例子,到目前为止,这似乎是扁平化嵌套层次结构的方法。但是,如果子对象具有许多属性,则此方法不会节省太多编码。

我找到了这个例子:

http://consultingblogs.emc.com/owainwragg/archive/2010/12/22/automapper-mapping-from-multiple-objects.aspx

但它需要 Map() 函数所需的映射对象的实例,据我了解,它不适用于配置文件。

我是 AutoMapper 的新手,所以我想知道是否有更好的方法来做到这一点。

4

7 回答 7

18

在最新版本的 AutoMapper 中,您可以使用一个命名约定来避免多个 .ForMember 语句。

在您的示例中,如果您将 Flattened 类更新为:

public class Flattened
{
    public string AParentProperty { get; set; }
    public string TheNestedClassANestedProperty { get; set; }
}

您可以避免使用 ForMember 语句:

Mapper.CreateMap<Root, Flattened>();

在这种情况下,Automapper 将(按照惯例)映射Root.TheNestedClass.ANestedProperty到。Flattened.TheNestedClassANestedProperty老实说,当您使用真实的类名时,它看起来不那么难看!

于 2011-11-24T15:37:39.127 回答
14

我更喜欢避免使用旧的静态方法并这样做。

将我们的映射定义放入Profile中。我们先映射 Root,然后再应用 Nested 的映射。注意Context的使用。

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Root, Flattened>()
            .AfterMap((src, dest, context) => context.Mapper.Map(src.TheNestedClass, dest));
        CreateMap<Nested, Flattened>();
    }
}

定义从RootFlattenedNestedFlatterned的映射的优点是您可以完全控制属性的映射,例如目标属性名称是否不同或您想要应用转换等。

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注入到应用映射的任何位置。

于 2019-02-21T08:38:23.460 回答
5

还有2个可能的解决方案:

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));
于 2014-10-01T08:50:21.533 回答
4

不确定这是否会为以前的解决方案增加价值,但您可以将其作为两步映射来完成。如果父子之间存在命名冲突(最后获胜),请注意以正确的顺序映射。

        Mapper.CreateMap<Root, Flattened>();
        Mapper.CreateMap<Nested, Flattened>();

        var flattened = new Flattened();
        Mapper.Map(root, flattened);
        Mapper.Map(root.TheNestedClass, flattened);
于 2018-05-22T11:59:35.480 回答
2

要改进另一个答案,请指定MemberList.Source两个映射并将嵌套属性设置为被忽略。验证然后通过 OK。

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);
于 2018-03-07T14:59:14.090 回答
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()是从这里

虽然它不是通用的解决方案,但对于简单的情况应该足够了。

所以,

  1. 您无需遵循目的地属性中的展平约定
  2. Mapper.AssertConfigurationIsValid() 将通过
  3. 您也可以在非静态 API(配置文件)中使用此方法
于 2016-05-23T10:53:01.217 回答
0

我创建了一个简单的示例,使用 AutoMappers 新的命名约定规则从扁平对象映射到嵌套对象,希望这会有所帮助

https://dotnetfiddle.net/i55UFK

于 2020-11-30T16:31:45.967 回答