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

C# 使用AutoMapper展平嵌套对象的更好方法?,c#,nested,automapper,flatten,C#,Nested,Automapper,Flatten,我一直在将域对象展平为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 s

我一直在将域对象展平为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);
公共类根目录
{
公共字符串AParentProperty{get;set;}
公共嵌套的TheNestedClass{get;set;}
}
公共类嵌套
{
公共字符串属性{get;set;}
}
公务舱被夷为平地
{
公共字符串AParentProperty{get;set;}
公共字符串属性{get;set;}
}
//在应用程序启动时配置的配置文件中,我添加了与以下内容等效的内容
//正如其他人所建议的那样:
Mapper.CreateMap()
福门博先生
(
dest=>dest.ANestedProperty
,opt=>opt.MapFrom(src=>src.TheNestedClass.ANestedProperty)
);
//这在我的控制器中:
展平MyFlatted=Mapper.Map(myRoot);
我已经看了很多例子,到目前为止,这似乎是一种扁平化嵌套层次结构的方法。但是,如果子对象具有多个属性,则这种方法不会节省太多编码

我发现这个例子:

但它需要Map()函数所需的映射对象的实例,据我所知,该函数不适用于概要文件


我是汽车制造商的新手,所以我想知道是否有更好的方法来做到这一点

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

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

public class Flattened
{
    public string AParentProperty { get; set; }
    public string TheNestedClassANestedProperty { get; set; }
}
您可以避免使用FormMember语句:

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

自动映射将(按照约定)将
根.TheNestedClass.ANestedProperty
映射到
展平的.TheNestedClassANestedProperty
。老实说,当你使用真实的类名时,它看起来不那么难看

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

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

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;
}
publicstaticimappingexpression(
这个IMappingExpression表达式,
表达式嵌套选择器,
IMappingExpression嵌套的映射表达式)
{
var dstProperties=typeof(tdestinition).GetProperties().Select(p=>p.Name);
var flattedMappings=nestedMappingExpression.TypeMap.GetPropertyMaps()
.Where(pm=>pm.IsMapped()&&!pm.IsIgnored())
.ToDictionary(pm=>pm.DestinationProperty.Name,
pm=>Expression.Lambda(
表达式.MakeMemberAccess(nestedSelector.Body,pm.SourceMember),
nestedSelector.Parameters[0]);
foreach(dstProperties中的var属性)
{
如果(!FlattedMappings.ContainsKey(属性))
继续;
expression.ForMember(属性,opt=>opt.MapFrom((动态)flattedMappings[property]);
}
返回表达式;
}
因此,在您的情况下,它可以这样使用:

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

Mapper.CreateMap<Root, Flattened>()
      .FlattenNested(s => s.TheNestedClass, nestedMap);
var nestedMap=Mapper.CreateMap()
.ignoreall不存在();
Mapper.CreateMap()
.flatternested(s=>s.TheNestedClass,nestedMap);
ignoreallnoexisting()
来自

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

所以

  • 您不需要在目的地的属性中遵循展平约定
  • assertConfigurationsValid()将通过
  • 您也可以在非静态API(概要文件)中使用此方法

  • 要改进另一个答案,请为这两个映射指定
    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);
    
    Mapper.Initialize(cfg=>
    {
    CreateMap(MemberList.Source);
    CreateMap(MemberList.Source)
    .ForSourceMember(s=>s.Nested,x=>x.Ignore())
    .AfterMap((s,d)=>Mapper.Map(s.Nested,d));
    });
    assertConfigurationsValid();
    var dest=Mapper.Map(src);
    
    不确定这是否为以前的解决方案增加了价值,但您可以通过两步映射来实现。如果父级和子级之间存在命名冲突(最后一次获胜),请小心按正确顺序映射

    Mapper.CreateMap();
    CreateMap();
    var展平=新展平();
    Mapper.Map(根,展平);
    Map(root.TheNestedClass,展平);
    
    我更喜欢避免使用旧的静态方法,而是这样做

    将映射定义放入配置文件中。我们首先映射根,然后应用嵌套对象的映射。注意上下文的用法

    公共类映射配置文件:配置文件
    {
    公共映射配置文件()
    {
    CreateMap()
    .AfterMap((src,dest,context)=>context.Mapper.Map(src.TheNestedClass,dest));
    CreateMap();
    }
    }
    
    定义从根到展平和嵌套到展平的映射的优点是,您可以保留对属性映射的完全控制,例如,如果目标属性名称不同,或者希望应用转换等

    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);
    }
    
    [事实]
    公共空心映射\u根\u到\u扁平\u应\u包括\u嵌套的\u属性()
    {
    //安排
    var myRoot=新根
    {
    AParentProperty=“我的AParentProperty”,
    TheNestedClass=新嵌套
    {
    ANestedProperty=“我的ANestedProperty”
    }
    };
    //使用配置文件手动创建映射器
    var mapper=new-MapperConfiguration(cfg=>cfg.AddProfile(new-MappingProfile()).CreateMapper();
    //表演
    var myflatted=mapper.Map(myRoot);
    //断言
    Assert.Equal(myRoot.AParentProperty、myflatted.AParentProperty);
    Assert.Equal(myRoot.TheNestedClass.ANestedProperty、MyFlatted.ANestedProperty);
    }
    
    通过从自动添加AutoMapper的serviceCollection.AddAutoMapper()
            Mapper.CreateMap<Root, Flattened>();
            Mapper.CreateMap<Nested, Flattened>();
    
            var flattened = new Flattened();
            Mapper.Map(root, flattened);
            Mapper.Map(root.TheNestedClass, flattened);
    
    public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<Root, Flattened>()
                .AfterMap((src, dest, context) => context.Mapper.Map(src.TheNestedClass, dest));
            CreateMap<Nested, Flattened>();
        }
    }
    
    [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);
    }