简体   繁体   English

使用 AutoMapper 展平嵌套对象的更好方法?

[英]A better way to use AutoMapper to flatten nested objects?

I have been flattening domain objects into DTOs as shown in the example below:我一直在将域对象扁平化为 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);

I have looked at a number of examples, and so far this seems to be the way to flatten a nested hierarchy.我看过一些例子,到目前为止,这似乎是扁平化嵌套层次结构的方法。 If the child object has a number of properties, however, this approach doesn't save much coding.但是,如果子 object 具有许多属性,则这种方法并不能节省太多编码。

I found this example:我找到了这个例子:

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

but it requires instances of the mapped objects, required by the Map() function, which won't work with a profile as I understand it.但它需要 Map() function 所需的映射对象的实例,据我了解,它不适用于配置文件。

I am new to AutoMapper, so I would like to know if there is a better way to do this.我是 AutoMapper 的新手,所以我想知道是否有更好的方法来做到这一点。

In the latest version of AutoMapper, there's a naming convention you can use to avoid multiple.ForMember statements.在最新版本的 AutoMapper 中,您可以使用一个命名约定来避免使用多个 .ForMember 语句。

In your example, if you update your Flattened class to be:在您的示例中,如果您将 Flattened class 更新为:

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

You can avoid the use of the ForMember statement:您可以避免使用 ForMember 语句:

Mapper.CreateMap<Root, Flattened>();

Automapper will (by convention) map Root.TheNestedClass.ANestedProperty to Flattened.TheNestedClassANestedProperty in this case.在这种情况下,Automapper 将(按照惯例) map Root.TheNestedClass.ANestedPropertyFlattened.TheNestedClassANestedProperty It looks less ugly when you're using real class names, honest!老实说,当您使用真正的 class 名称时,它看起来不那么难看!

I much prefer avoiding the older Static methods and do it like this.我更喜欢避免使用旧的 Static 方法并这样做。

Place our mapping definitions into a Profile .将我们的映射定义放入Profile中。 We map the Root first, and then apply the mappings of the Nested afterwards.我们先 map Root,然后再应用 Nested 的映射。 Note the use of the Context .注意Context的使用。

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

The advantage of defining both the mapping from Root to Flattened and Nested to Flatterned is that you retain full control over the mapping of the properties, such as if the destination property name is different or you want to apply a transformation etc.定义从RootFlattenedNestedFlatterned的映射的优点是您可以完全控制属性的映射,例如目标属性名称是否不同或您想要应用转换等。

An XUnit test: 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);
}

By adding AutoMapper's serviceCollection.AddAutoMapper() from the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package to your start up, the Profile will be picked up automatically, and you can simply inject IMapper into wherever you are applying the mapping.通过从AutoMapper.Extensions.Microsoft.DependencyInjection nuget package 添加 AutoMapper 的serviceCollection.AddAutoMapper()到您的启动,配置文件将被自动拾取,您可以简单地将IMapper注入到您应用映射的任何位置。

2 more possible solutions:还有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));

An alternative approach would be the below, but it would not pass the Mapper.AssertConfigurationIsValid() .另一种方法如下,但它不会通过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));

Not sure if this adds value to the previous solutions, but you could do it as a two-step mapping.不确定这是否会为以前的解决方案增加价值,但您可以将其作为两步映射来完成。 Be careful to map in correct order if there are naming conflicts between the parent and child (last wins).如果父子之间存在命名冲突(最后获胜),请注意按正确顺序排列 map。

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

        var flattened = new Flattened();
        Mapper.Map(root, flattened);
        Mapper.Map(root.TheNestedClass, flattened);

To improve upon another answer, specify MemberList.Source for both mappings and set the nested property to be ignored.要改进另一个答案,请为两个映射指定MemberList.Source并将嵌套属性设置为忽略。 Validation then passes OK.验证然后通过 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);

I wrote extension method to solve similar problem:我写了扩展方法来解决类似的问题:

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;
}

So in your case it can be used like this:所以在你的情况下,它可以像这样使用:

var nestedMap = Mapper.CreateMap<Nested, Flattened>()
                      .IgnoreAllNonExisting();

Mapper.CreateMap<Root, Flattened>()
      .FlattenNested(s => s.TheNestedClass, nestedMap);

IgnoreAllNonExisting() is from here . IgnoreAllNonExisting()来自这里

Though it's not universal solution it should be enough for simple cases.虽然它不是通用的解决方案,但对于简单的情况应该足够了。

So,所以,

  1. You don't need to follow flattening convention in destination's properties您无需遵循目的地属性中的展平约定
  2. Mapper.AssertConfigurationIsValid() will pass Mapper.AssertConfigurationIsValid() 将通过
  3. You can use this method in non-static API (a Profile) as well您也可以在非静态 API(配置文件)中使用此方法

I created a simple example using AutoMappers new naming convention rules to map from flattened to nested objects, hope this helps我创建了一个简单的例子,使用 AutoMappers 新的命名约定规则到 map 从扁平对象到嵌套对象,希望这会有所帮助

https://dotnetfiddle.net/i55UFK https://dotnetfiddle.net/i55UFK

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM