AutoMapper: Incorrect mapping in case of multiple interfaces
Automapper version 4.2.1 In case when source implements more than one interface and we do not have mapping configuration for the source but have it for interfaces it will be impossible to use mapping configured for second (third, fourth…) interface
public interface IChildA
{
string PropertyA { get; set; }
}
public interface IChildB
{
string PropertyB { get; set; }
}
public class Source : IChildA, IChildB
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
public class Destination
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
...
public void MappingTest()
{
var conf = new MapperConfiguration(
config =>
{
config.CreateMap<IChildA, Destination>();
config.CreateMap<IChildB, Destination>();
});
var mapper = conf.CreateMapper();
var src = new Source { PropertyA = "A", PropertyB = "B" };
var dest = new Destination();
mapper.Map<IChildA, Destination>(src, dest); // will map PropertyA
mapper.Map<IChildB, Destination>(src, dest); // will NOT map PropertyB
Trace.WriteLine(dest.PropertyA == src.PropertyA); // true
Trace.WriteLine(dest.PropertyB == src.PropertyB); // false !!!
}
The code above will not fill PropertyB (It will in case we will change order of interfaces in the Source class declaration)
The reason is MapperConfiguration.ResolveTypeMap method:
public TypeMap ResolveTypeMap(object source, object destination, Type sourceType, Type destinationType)
{
return ResolveTypeMap(source?.GetType() ?? sourceType, destination?.GetType() ?? destinationType);
}
It will call source.GetType() and then will look for the first known mapping (it will be IChildA in our case), so “sourceType” (IChildB in our case) will be ignored and we will got invalid mapping.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Comments: 26 (13 by maintainers)
Just a heads up that I’m still on this thing, just havn’t found any time to actually write the code
A lot of stuff went on here while I was sleeping 😃
@oddbear you’re right about the order of interfaces, as I said, it was just a quick thought. I like your approach as it eliminates the dependency on ValueTuple.
I have some thoughts on this issue, or at least some ways I’d like to see it implemented. I think I might be able to conceive most of it through some extension methods, but I’d really have to bunker down and hammer out the code to be sure. Without changing the core it might have some uglyish parts.
I was thinking of having something like a MapAs method taking one or more type parameters to use explicitly. Having a separate method should enhance readability. @jbogard, what do you mean with a global config? That doesn’t sound very useful, as in some case one might still want to use the default behaviour.
Anyways, more on this when I got down with some code. I don’t think I’ll have much time for it today, but maybe I could put together a little POC by tomorrow or thursday.
With something like this, where it’s not a bug but a feature request, I’m open to PRs. I wouldn’t use this functionality on my projects, which means for me its priority is zero. If someone can get it to work without breaking existing functionality, I’m happy to entertain a PR.
Quick thought on a workaround. @oddbear 's idea about proxies got me thinking.
I don’t want to create a proxy for every possible interface combo, so I wondered: can this be fixed using generics? Turns out this is easily achieved using the magic of ValueTuple.
Consider the following map:
I can now easily map my model as such:
Of course you’ll still need to specify the different mapping combo’s, but at least you won’t have a bunch of singular-use proxies cluttering up your project.
Given this any more thoughts?
We have the same type of issues.
There is so much strange behavior on this scenario. Interface order on the class picks the mapping profile, and this might change based on the source object type.
The only workaround I have found is to use mapping to a Proxy in between. That is, not map A to B, but A to proxy of interface, to instance of B:
I will add one more example
Domain:
Test:
So mapping strategy depends from order of interfaces (!!!). Source_A_B mapped one way while Source_B_A mapped another. From my point of view it is a mistake to have a code dependent from interface ordering.