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

发布于 2024-11-18 16:42:25 字数 1207 浏览 1 评论 0原文

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

I have been flattening domain objects into DTOs as shown in the example below:

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.

I found this example:

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.

I am new to AutoMapper, so I would like to know if there is a better way to do this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

昇り龍 2024-11-25 16:42:25

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

将我们的映射定义放入配置文件中。我们首先映射 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 包添加到您的启动中,配置文件将自动获取,您只需将 IMapper 注入到应用映射的任何位置即可。

I much prefer avoiding the older Static methods and do it like this.

Place our mapping definitions into a Profile. We map the Root first, and then apply the mappings of the Nested afterwards. Note the use of the 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.

An XUnit test:

[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.

半世蒼涼 2024-11-25 16:42:25

在最新版本的 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< /代码> 在这种情况下。老实说,当您使用真实的类名时,它看起来不那么难看!

In the latest version of AutoMapper, there's a naming convention you can use to avoid multiple .ForMember statements.

In your example, if you update your Flattened class to be:

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

You can avoid the use of the ForMember statement:

Mapper.CreateMap<Root, Flattened>();

Automapper will (by convention) map Root.TheNestedClass.ANestedProperty to Flattened.TheNestedClassANestedProperty in this case. It looks less ugly when you're using real class names, honest!

毁梦 2024-11-25 16:42:25

还有 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));

2 more possible solutions:

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.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));
冧九 2024-11-25 16:42:25

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

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

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

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).

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

        var flattened = new Flattened();
        Mapper.Map(root, flattened);
        Mapper.Map(root.TheNestedClass, flattened);
痴梦一场 2024-11-25 16:42:25

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

To improve upon another answer, specify MemberList.Source for both mappings and set the nested property to be ignored. Validation then passes 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);
一江春梦 2024-11-25 16:42:25

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

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(配置文件)中使用此方法

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.

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
  3. You can use this method in non-static API (a Profile) as well
ヅ她的身影、若隐若现 2024-11-25 16:42:25

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

https://dotnetfiddle。网/i55UFK

I created a simple example using AutoMappers new naming convention rules to map from flattened to nested objects, hope this helps

https://dotnetfiddle.net/i55UFK

风向决定发型 2024-11-25 16:42:25

对于那些在这里绊倒寻找映射嵌套对象属性的方法,同时还利用 < code>属性映射,我希望您能够从以下内容中获得一些有价值的东西:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class NestedSourceMemberAttribute : Attribute, IMemberConfigurationProvider
{
    public string? PropertyName { get; }

    public NestedSourceMemberAttribute(params string[] nestedParams) {
        PropertyName = string.Join('.', nestedParams);
    }

    public void ApplyConfiguration(IMemberConfigurationExpression memberConfigurationExpression)
    {
        memberConfigurationExpression.MapFrom(PropertyName);
    }
}

用法如下:

    [NestedSourceMember(nameof(ParentEntity.NestedEntity), nameof(NestedEntity.Property))]
    public string? PropertyValue { get; set; }

利用params string[]允许真正的任何深度的映射,继续添加即可参数越深,你需要走得越深。

For anyone who stumbles here looking for a way to map Nested object properties while also leveraging Attribute Mapping, I am hoping you are able to pull something valuable from the following:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class NestedSourceMemberAttribute : Attribute, IMemberConfigurationProvider
{
    public string? PropertyName { get; }

    public NestedSourceMemberAttribute(params string[] nestedParams) {
        PropertyName = string.Join('.', nestedParams);
    }

    public void ApplyConfiguration(IMemberConfigurationExpression memberConfigurationExpression)
    {
        memberConfigurationExpression.MapFrom(PropertyName);
    }
}

Usage is as follows:

    [NestedSourceMember(nameof(ParentEntity.NestedEntity), nameof(NestedEntity.Property))]
    public string? PropertyValue { get; set; }

Leveraging the params string[] allows for really any depth of mapping, just continue to add the params the deeper you need to go.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文