【问题标题】:A better way to use AutoMapper to flatten nested objects?使用 AutoMapper 展平嵌套对象的更好方法?
【发布时间】:2011-09-28 09:46:57
【问题描述】:

我一直在将域对象扁平化为 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 的新手,所以我想知道是否有更好的方法来做到这一点。

【问题讨论】:

    标签: c# nested automapper flatten


    【解决方案1】:

    在最新版本的 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。老实说,当你使用真实的类名时,它看起来不那么难看!

    【讨论】:

    • 根据 Automapper 的使用来命名我的视图模型属性不是我想做的事情。我很欣赏这个答案,但解决方案的副作用会让我在原始问题中使用该技术。
    【解决方案2】:

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

    将我们的映射定义放入一个配置文件。我们先映射 Root,然后再应用 Nested 的映射。注意上下文的使用。

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

    【讨论】:

      【解决方案3】:

      另外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));
      

      【讨论】:

      • 不错的方法;太糟糕了,在此配置上调用 Mapper.AssertConfigurationIsValid(); 失败并出现两个错误(创建了两个映射,没有一个完全覆盖目标类型的属性)
      • 如果嵌套类中有很多字段,不确定如何解决问题?如果 Flattened 对象中有多个应该从嵌套对象映射的属性,则 OP 希望避免添加多个“Fordestination Member”语句。
      • 这只是命名约定方法的一种替代方法。并非所有时候都有可能一直更改/重构属性名称以符合 AutoMapper 的命名约定。
      • 第二次通过.AfterMap 步骤是我所需要的——谢谢!
      【解决方案4】:

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

              Mapper.CreateMap<Root, Flattened>();
              Mapper.CreateMap<Nested, Flattened>();
      
              var flattened = new Flattened();
              Mapper.Map(root, flattened);
              Mapper.Map(root.TheNestedClass, flattened);
      

      【讨论】:

        【解决方案5】:

        要改进另一个答案,请为两个映射指定 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);
        

        【讨论】:

          【解决方案6】:

          我写了扩展方法来解决类似的问题:

          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

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

          所以,

          1. 您无需遵循目标属性中的扁平化约定
          2. Mapper.AssertConfigurationIsValid() 将通过
          3. 您也可以在非静态 API(配置文件)中使用此方法

          【讨论】:

          • 我无法使用最新版本的 AutoMapper 使其工作。 MappingExpression.TypeMap 不再可用。
          【解决方案7】:

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

          https://dotnetfiddle.net/i55UFK

          【讨论】:

            猜你喜欢
            • 2017-07-22
            • 1970-01-01
            • 1970-01-01
            • 2020-04-15
            • 1970-01-01
            • 2013-10-06
            • 1970-01-01
            • 2012-10-31
            • 2018-10-24
            相关资源
            最近更新 更多