Automapper: 6.2.0 Broke Conventions-Based Mappings With Missing Target Members

Created on 15 Nov 2017  Â·  12Comments  Â·  Source: AutoMapper/AutoMapper

Version: 6.2.0

This works fine in v6.1.1

Expected behavior

The mapping happens successfully and FooViewModel.Extra is null

Actual behavior

Exception:

AutoMapper.AutoMapperConfigurationException : 
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
================================================================
Foo -> FooViewModel (Destination member list)
AutoMapper.Bug.Class2+Foo -> AutoMapper.Bug.Class2+FooViewModel (Destination member list)

Unmapped properties:
Extra

Steps to reproduce

public class Foo
{
    public string Bar { get; set; }
}

public class FooViewModel
{
    public string Bar { get; set; }
    public string Extra { get; set; }
}

public class ViewModelConvention : Profile
{
    /// <summary>
    /// Initialises a new instance of the <see cref="ViewModelConvention"/> class.
    /// </summary>
    public ViewModelConvention()
    {
        AddConditionalObjectMapper().Where((source, dest) => dest.Name == source.Name + "ViewModel");
    }
}

[Fact]
public void TestMappingWithMissingTargetProperty()
{
    Mapper.Initialize(cfg => cfg.AddProfile<ViewModelConvention>());

    var result = Mapper.Map<FooViewModel>(new Foo { Bar = "Baz" });

    result.Bar.Should().Be("Baz");
}
Bug

All 12 comments

Duplicate of #2398.

@lbargaoanu But that's a breaking change introduced in a minor version update. I've also followed the link through to the Inline Mapping documentation and that doesn't show you how to ignore the target members in a conditional mapper.

cc @jbogard

I think the problem was that convention maps assumed you'd always want to validate the destination members list. So this....accidentally worked before because the convention maps in your case wouldn't get validated, UNLESS you happened to assert configuration after runtime created some maps.

I'm working on a fix that lets you specify what members to validate (source, destination, none). In the meantime you can add an Ignore attribute to members you want to ignore.

Basically we're trying to make inline/convention maps safe by default to fit with the rest of AutoMapper's design philosophy.

@jbogard OK, that's fine. Can we re-open the issue then until that change is made because it's currently not possible to recreate the old behaviour without having to go through a codebase and add lots of Ignore attributes everywhere?

I've also got a problem in that instance because I've got a single ViewModel which is effectively the concatenation of two other Dto classes, and I was using the fact that it only maps the relevant properties to construct the ViewModel, like this:

var foo = new Foo { Name = "X" };
var bar = new Bar { Colour = "Blue" };

var fooBar = Mapper.Map<FooBarViewModel>(foo); // map Foo-related properties only
Mapper.Map(bar, fooBar); // add in Bar-related properties

// make sure the result is the concatenation of the two
fooBar.Name.Should().Be("X");
fooBar.Colour.Should().Be("Blue");

I can't add any Ignore attributes to FooBarViewModel or this will stop working.

The original behavior is to turn inline map validation off:

http://docs.automapper.org/en/stable/Inline-Mapping.html#inline-validation

The next question I would have is - how do you know if your mapping is correct?

@adamrodger I've pushed a PR that lets you configure the member list, so you could configure the type maps to ignore validation for those type maps #2406

@adamrodger I've pushed a PR that lets you configure the member list, so you could configure the type maps to ignore validation for those type maps #2406

Is there a way to configure the default member list for a MapperConfiguration, rather than on individual type maps? Something like this:

var config = new MapperConfiguration(cfg => cfg.MemberList = MemberList.Source);
var mapper = config.CreateMapper();

You can try ForAllMaps.

You can try ForAllMaps.

Thanks!

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vadimshaporov picture vadimshaporov  Â·  7Comments

vivainio picture vivainio  Â·  6Comments

zolakt picture zolakt  Â·  4Comments

colin-young picture colin-young  Â·  6Comments

Mds92 picture Mds92  Â·  5Comments