AutoMapper:“忽略其余部分”?

245

有没有一种方法可以告诉AutoMapper忽略除了明确定义映射的属性以外的所有属性?

我有一些外部的DTO类,这些类可能会从外部发生变化,我想避免明确指定要忽略每个属性,因为添加新属性时,将尝试将它们映射到我的对象中会导致功能出现问题(引起异常)。


1
通过ValueInjecter(http://valueinjecter.codeplex.com/documentation),您可以创建具有其映射算法的ValueInjections,用于在特定属性之间进行映射,而不关心其余属性。 - Omu
33
如果您使用的是 Automapper 5 及以上版本,请跳至下面查看有关 .ForAllOtherMembers(opts => opts.Ignore()) 的答案。 - Jack Ukleja
2
@Schneider ".ForAllOtherMembers(opts => opts.Ignore())" 和扩展名"IgnoreAllNonExisting"不同,主要区别在于如果您没有显式配置属性,则使用".ForAllOtherMembers(opts => opts.Ignore())"将无法映射任何内容。而使用"IgnoreAllNonExisting"时,即使没有显式配置属性,仍然会将一些具有相同名称的属性映射为值。 - Dragon
是的。对于其他所有成员,ForAllOtherMembers 可以解决问题。IgnoreUnmapped 不会有任何作用,因为未映射的成员会被忽略,只会导致 config-valid-assert 通过。 - N73k
1
值得注意的是,在执行此操作时,您明确隐藏了可能与映射的类相关或重要的更改。为每个属性设置显式映射将在映射的类发生更改时留下一个破损的测试,迫使您对其进行适当评估(假设您有一个执行“AssertConfigurationIsValid()”调用的测试)。因此,我认为“忽略其余部分”是一种反模式。 - Arve Systad
AutoMapper 11的解决方案是https://dev59.com/Z8Tra4cB1Zd3GeqP8XeF#73333328。 - Michael Freidgeim
23个回答

334

据我所理解,问题是目标上有一些字段没有在源中进行映射,因此您正在寻找忽略这些未映射目标字段的方法。

您可以简单地使用而不是实现和使用这些扩展方法。

Mapper.CreateMap<sourceModel, destinationModel>(MemberList.Source);  

现在自动映射知道它只需要验证所有源字段是否已映射,而不是反过来。

你也可以使用:

Mapper.CreateMap<sourceModel, destinationModel>(MemberList.Destination);  

13
这个回答应该有更多的赞,甚至可以被标记为答案。它解决了我的问题,类似地,“MemberList.Destination”也能解决ops的问题。 - Tedd Hansen
1
如果您想在源和目标上忽略几个属性,则此方法将无效 :) - RealWillyWonka
84
对于那些之后来的人,这是5.0的正确答案。 - Jimmy Bogard
5
看起来很漂亮,但对我没用。我尝试了源和目标,但它一直抱怨同一个属性对象缺少映射。 - Sonic Soul
5
使用6.0.2版本,但这根本不起作用。任何未从目标到源映射的属性,都将用null和0来覆盖源中的属性。此外,代码不清楚你在做什么,特别是当你在团队中工作时。这就是为什么我非常不喜欢这段代码,并且更喜欢像建议的答案"IgnoreAllNonExisting"这样的选择性词语。 - sksallaj
显示剩余9条评论

228
我已经更新了Can Gencer的扩展程序,不会覆盖任何现有的地图。
public static IMappingExpression<TSource, TDestination> 
    IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
    var sourceType = typeof (TSource);
    var destinationType = typeof (TDestination);
    var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType.Equals(sourceType) && x.DestinationType.Equals(destinationType));
    foreach (var property in existingMaps.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}

用法:

Mapper.CreateMap<SourceType, DestinationType>()
                .ForMember(prop => x.Property, opt => opt.MapFrom(src => src.OtherProperty))
                .IgnoreAllNonExisting();

更新:请注意,在版本9.0中,映射器的静态API已被删除


5
谢谢你发布这个解决方案,因为我使用http://goo.gl/rG7SL中的解决方案时遇到了一个奇怪的bug,直到我再次偶然发现这篇帖子,花了我数小时才弄明白。 - Nordin
3
使用静态Mapper类。当您通过IoC配置了AutoMapper时,需要获取IConfigurationProvider以获取所有类型映射。 - Martijn B
4
在AutoMapper 4.2中能否完成此操作?(Mapper.GetAllTypeMaps()已被弃用) - mrmashal
16
对于 AutoMapper 5+ 版本,只需将Mapper.GetAllTypeMaps()替换为Mapper.Configuration.GetAllTypeMaps()即可。这是参考网址github.com/AutoMapper/AutoMapper/issues/1252。 - SerjG
6
对于初次接触此话题的读者,本回答是针对 AutoMapper 2 版本的。截至本回答撰写时,AutoMapper 已更新至版本 6。本回答提供了一种 hack 方法,而更为简洁的方法是使用 MemberList 枚举类型。请参阅 Github 问题编号1839,以获取更好的解决方案。 示例链接: https://dev59.com/3nNA5IYBdhLWcg3wdthd#31182390 - Ogglas
显示剩余8条评论

106
更新:对于使用最新版本的Automapper的人来说,这个答案已经不再有用,因为ForAllOtherMembers在Automapper 11中被移除了。


Automapper的5.0.0-beta-1版本引入了ForAllOtherMembers扩展方法,使您现在可以执行以下操作:

CreateMap<Source, Destination>()
    .ForMember(d => d.Text, o => o.MapFrom(s => s.Name))
    .ForMember(d => d.Value, o => o.MapFrom(s => s.Id))
    .ForAllOtherMembers(opts => opts.Ignore());

请注意,明确映射每个属性的好处是您永远不会遇到映射失败的问题,这种问题可能在您忘记映射属性时出现。

也许在您的情况下,忽略所有其他成员并添加TODO以在此类更改频率稳定后回来进行明确映射可能是明智的选择。


4
令人惊讶的是这个功能要到第五个版本才推出。看看这个问题有多少点赞和尝试回答……我想知道 Automapper 的治理是否存在问题? - Jack Ukleja
2
你甚至可以先放置ForAllOtherMembers行,效果是一样的,这对于具有某种基类配置的情况非常有用。 - N73k
2
在源对象中是否有忽略属性的等效方法?类似于 ForAllOtherSourceMembers 这样的东西? - SuperJMN
1
@SuperJMN,这里有一个名为MemberList的枚举类型,你只需要将它作为参数放入到CreateMap方法中,就像这样:CreateMap<Source, Destination>(MemberList.Source) - akim lyubchenko
2
即使它回答了这个问题,Jimmy Bogard解释说,ForAllOtherMembers(opts => opts.Ignore())破坏了Automapper的目的。考虑使用IgnoreUnmapped<Src, Dest>()仍然按照惯例映射成员,并避免从AssertConfigurationIsValid()中发出警报。 - Michael Freidgeim
显示剩余3条评论

89

这是我编写的一个扩展方法,它会忽略目标对象上所有不存在的属性。不确定它是否仍然有用,因为这个问题已经过去两年了,但我遇到了同样的问题,需要添加许多手动的 Ignore 调用。

public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>
(this IMappingExpression<TSource, TDestination> expression)
{
    var flags = BindingFlags.Public | BindingFlags.Instance;
    var sourceType = typeof (TSource);
    var destinationProperties = typeof (TDestination).GetProperties(flags);

    foreach (var property in destinationProperties)
    {
        if (sourceType.GetProperty(property.Name, flags) == null)
        {
            expression.ForMember(property.Name, opt => opt.Ignore());
        }
    }
    return expression;
}

使用方式:

Mapper.CreateMap<SourceType, DestinationType>()
                .IgnoreAllNonExisting();

更新:显然,如果您有自定义映射,这种方法不起作用,因为它会覆盖自定义映射。我想,如果先调用IgnoreAllNonExisting,然后再调用自定义映射,则仍然可以使用该方法。

schdr提供了一种解决方案(作为此问题的答案),它使用Mapper.GetAllTypeMaps()查找未映射的属性并自动忽略它们。对我来说,这似乎是一种更可靠的解决方案。


我已经有一段时间没有使用AutoMapper了,但如果它对你有用的话,我会接受你的答案 :)。 - Igor Brejc
2
谢谢!我发现这非常方便。在我的情况下,逐个忽略属性是违背使用automapper的初衷的。 - Daniel Robinson
3
这个方法应该放在autoMapper本地代码中!非常好,谢谢! - Felipe Oriani
3
FYI,AutoMapper 作者 Jimmy 在下面评论说,@nazim 的答案对于版本 5+ 是正确的。 - Worthy7
@JasonCoyne,十年过去了,SO已经实现了答案的动态排序。你还记得哪个答案是“下一个”吗? - Björn Larsson
显示剩余3条评论

87

我能够通过以下方式实现这个:

Mapper.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Ignore());
Mapper.CreateMap<SourceType, DestinationType>().ForMember(/*Do explicit mapping 1 here*/);
Mapper.CreateMap<SourceType, DestinationType>().ForMember(/*Do explicit mapping 2 here*/);
...

注意:我正在使用AutoMapper v.2.0。


5
非常感谢!它非常好用。我先尝试链接这些调用,但是ForAllMembers只返回空值 :(. 之前的IgnoreAll还可以修改,这一点不太明显。 - SeriousM
7
我也不喜欢这种方式。如果你有50个成员,并且您想忽略25个成员,那么使用自动映射器还有什么意义呢?如果名称匹配,但是有一些属性不匹配,为什么不明确告诉自动映射器在未映射属性上不进行匹配,并通过传递所有类型来完成匹配呢? - sksallaj

45

从AutoMapper 5.0开始,IMappingExpression上的 .TypeMap 属性已被删除,这意味着4.2版本的解决方案不再可用。我创建了一个解决方案,它使用原始功能但具有不同的语法:

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Src, Dest>();
    cfg.IgnoreUnmapped();        // Ignores unmapped properties on all maps
    cfg.IgnoreUnmapped<Src, Dest>();  // Ignores unmapped properties on specific map
});

// or add  inside a profile
public class MyProfile : Profile
{
   this.IgnoreUnmapped();
   CreateMap<MyType1, MyType2>();
}

实现:

public static class MapperExtensions
{
    private static void IgnoreUnmappedProperties(TypeMap map, IMappingExpression expr)
    {
        foreach (string propName in map.GetUnmappedPropertyNames())
        {
            if (map.SourceType.GetProperty(propName) != null)
            {
                expr.ForSourceMember(propName, opt => opt.Ignore());
            }
            if (map.DestinationType.GetProperty(propName) != null)
            {
                expr.ForMember(propName, opt => opt.Ignore());
            }
        }
    }

    public static void IgnoreUnmapped(this IProfileExpression profile)
    {
        profile.ForAllMaps(IgnoreUnmappedProperties);
    }

    public static void IgnoreUnmapped(this IProfileExpression profile, Func<TypeMap, bool> filter)
    {
        profile.ForAllMaps((map, expr) =>
        {
            if (filter(map))
            {
                IgnoreUnmappedProperties(map, expr);
            }
        });
    }

    public static void IgnoreUnmapped(this IProfileExpression profile, Type src, Type dest)
    {
        profile.IgnoreUnmapped((TypeMap map) => map.SourceType == src && map.DestinationType == dest);
    }

    public static void IgnoreUnmapped<TSrc, TDest>(this IProfileExpression profile)
    {
        profile.IgnoreUnmapped(typeof(TSrc), typeof(TDest));
    }
}

3
你如何在 Profile 中的链式 CreateMap<TSource,TDest>() 表达式中使用它? - jmoerdyk
2
谢谢。GetUnmappedPropertyNames方法返回所有未映射的属性名称,包括源和目标,这似乎在反向映射时会出现问题,因此我不得不对IgnoreUnmapped进行小修改,以检查未映射的属性是在源还是目标上,并相应地进行忽略。这里有一个演示问题和更新的fiddle:https://dotnetfiddle.net/vkRGJv - Mun
1
我已经更新了我的答案,并包含了你的发现 - 我不使用源映射,所以之前没有遇到过这个问题!谢谢。 - Richard
1
在没有反射的情况下,这在PCL上无法工作,GetProperty(propName)不存在。 - George Taskos
我不明白这怎么是问题的解决方案,或者它到底有什么作用。未映射的属性已经会被忽略 - 因为它们是未映射的。发帖人说“如何忽略除了显式映射之外的属性”。这意味着如果我有Src.MyProp和Dest.MyProp,那么该映射应该被忽略,除非对MyProp进行了显式的MapFrom和ForMember调用。因此,默认映射需要被忽略。这个解决方案唯一做的事情就是导致config-valid-assert通过 - 但你不需要它来使映射工作。 - N73k
嗨,Richard,感谢你的回答,它指导我找到了正确的解决方案来应对我的场景。我必须对IgnoreUnmappedProperties进行小改动,以进行GetField检查。我必须使用属性将一个新类映射到使用字段的旧类,并且GetProperty检查无法捕获它们。这是带有更改和测试用例的代码片段[链接]https://dotnetfiddle.net/WOj7MF - seetdev

18

如果您想跳过所有未映射的属性,请在您的配置文件末尾加上

.ForAllOtherMembers(x=>x.Ignore());

例如:

internal class AccountInfoEntityToAccountDtoProfile : Profile
{
    public AccountInfoEntityToAccountDtoProfile()
    {
        CreateMap<AccountInfoEntity, AccountDto>()
           .ForMember(d => d.Id, e => e.MapFrom(s => s.BankAcctInfo.BankAcctFrom.AcctId))
           .ForAllOtherMembers(x=>x.Ignore());
    }
}

在这种情况下,只有输出对象的 Id 字段会被解析,其他所有字段都将被跳过。起作用就像魔法一样,似乎我们不再需要任何棘手的扩展了!


这个方法真的有效吗?即使使用x=>x.UseDestinationValue(),我仍然得到所有其他成员并将它们设置为默认值...而不是原始值。 - juagicre

17

这个问题已经问了几年了,但是对我来说,使用当前版本的AutoMapper(3.2.1),这个扩展方法似乎更加简洁:

public static IMappingExpression<TSource, TDestination> IgnoreUnmappedProperties<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
    var typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
    if (typeMap != null)
    {
        foreach (var unmappedPropertyName in typeMap.GetUnmappedPropertyNames())
        {
            expression.ForMember(unmappedPropertyName, opt => opt.Ignore());
        }
    }

    return expression;
}

16

对于那些使用版本4.2.0及以上的非静态API的人,可以使用以下扩展方法(在AutoMapperExtensions类中找到此处):

// from https://dev59.com/3nNA5IYBdhLWcg3wdthd#6474397
public static IMappingExpression IgnoreAllNonExisting(this IMappingExpression expression)
{
    foreach(var property in expression.TypeMap.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}

重要的是,一旦静态API被删除,像Mapper.FindTypeMapFor这样的代码将不再起作用,因此需要使用expression.TypeMap字段。

8
从5.0版本开始,expression.TypeMap不再可用。这里是我在5.0中的解决方案 - Richard
我不得不使用 public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression) 来解决类型问题。 - Nick M

10

我已更新Robert Schroeder的AutoMapper 4.2答案。 对于非静态映射配置,我们不能使用Mapper.GetAllTypeMaps(),但是expression引用了所需的TypeMap

public static IMappingExpression<TSource, TDestination> 
    IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
    foreach (var property in expression.TypeMap.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}

在AutoMapper 5.0中无法工作。IMappingExpression上的.TypeMap属性不可用。对于5.+版本,请参见Richard's answer中的扩展。 - Michael Freidgeim
适用于 AM 4.2。 - Leszek P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接