Have come across an issue with AutoMapper (v9.0) using the incorrect mapping for an inherited class when mapping to an Entity Framework (v6.4) proxy class. It appears to be related to the order in which the mapping is executed, and seems to be related to some kind of caching of the maps used. Here is the Entity Framwork configuration:
public class MyDbContext : DbContext
{
public MyDbContext()
{
base.Configuration.ProxyCreationEnabled = true;
}
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
}
public class Post
{
[Key]
public int Id { get; set; }
public DateTime PostDate { get; set; }
public string Content { get; set; }
public string Keywords { get; set; }
public virtual Blog Blog { get; set; }
}
And my DTO classes:
public class PostDTO
{
public DateTime PostDate { get; set; }
public string Content { get; set; }
}
public class PostWithKeywordsDTO : PostDTO
{
public string Keywords { get; set; }
}
Mapping profile:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<PostDTO, Post>()
.ForMember(dest => dest.Keywords, opt => opt.MapFrom(src => "No Keywords Specified"));
CreateMap<PostWithKeywordsDTO, Post>()
.ForMember(dest => dest.Keywords, opt => opt.MapFrom(src => src.Keywords));
}
}
I'm attempting to map these DTO object onto a proxy of the 'Post' class which is generated either by fetching an existing Post record from the database or by creating a new proxy of the Post class using (note, I need to enable the proxy class creation for performance reasons in my app):
_myDbContext.Posts.Create();
Now, when I attempt to perform a map from the following postDTO and postWithKeywordsDTO objects to the proxy class:
var postDTO = new PostDTO
{
PostDate = DateTime.Parse("1/1/2000"),
Content = "Post #1"
};
var postWithKeywordsDTO = new PostWithKeywordsDTO
{
PostDate = DateTime.Parse("6/30/2005"),
Content = "Post #2",
Keywords = "C#, Automapper, Proxy"
};
var postProxy = mapper.Map(postDTO, _myDbContext.Posts.Create());
var postWithKeywordsProxy = mapper.Map(postWithKeywordsDTO, dbContext.Posts.Create());
the resulting proxy objects are (pseudo-json):
postProxy: {
PostDate: '1/1/2000',
Content: 'Post #1',
Keywords: 'No Keywords Specified'
}
postWithKeywordsProxy: {
PostDate: '6/30/2005',
Content: 'Post #2',
Keywords: 'No Keywords Specified'
}
Furthermore, if I use something like an inline ValueResolver in the mapping and put a breakpoint on the 'return' lines, I can see that the PostDTO -> Post mapping is being used in both cases, and the PostWithKeywords -> Post mapping isn't being hit at all.
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<PostDTO, Post>()
.ForMember(dest => dest.Keywords, opt => opt.MapFrom(src => "No Keywords Specified"))
.ForMember(dest => dest.Content, opt => opt.MapFrom((src, dest) =>
{
return src.Content; <-- Hit for both PostDTO and PostWithKeywordsDTO maps to Post
}))
;
CreateMap<PostWithKeywordsDTO, Post>()
.ForMember(dest => dest.Keywords, opt => opt.MapFrom(src => src.Keywords))
.ForMember(dest => dest.Content, opt => opt.MapFrom((src, dest) =>
{
return src.Content;
}))
;
}
}
What I take from this is that it appears that there is some kind of issue in identifying which Type Map to use when dealing with a Proxy object. It seems as though in the first scenario, it encounters an attempted map between PostDTO -> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 (proxy class), and correctly determines that the map to use is the PostDTO -> Post mapping. It then encounters the attempted map between PostWithKeywordsDTO -> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 and doesn't realize that the PostWithKeywordsDTO is actually a child of PostDTO, and mistakenly re-uses the PostDTO -> Post mapping.
What's odd, however, is what happens if I reverse the order in which the maps are executed:
var postWithKeywordsProxy = mapper.Map(postWithKeywordsDTO, dbContext.Posts.Create());
var postProxy = mapper.Map(postDTO, _myDbContext.Posts.Create());
the resulting proxy objects are correct:
postWithKeywordsProxy: {
PostDate: '6/30/2005',
Content: 'Post #2',
Keywords: 'C#, Automapper, Proxy'
}
postProxy: {
PostDate: '1/1/2000',
Content: 'Post #1',
Keywords: 'No Keywords Specified'
}
This makes me think it has to do with some kind of caching mechanism, which possibly looks for the first map it can find which satisfies the requested proxy map, even if it's not an exact match. In this case, the PostWithKeywordsDTO -> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 mapping happens first, such that when the subsequent PostDTO -> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 map happens, it's not able to find a cached Type Map which satisfies the parameters and it continues with generating the correct cached map.
I did attempt to use the version of the Map method which takes in the explicit types of the items to be mapped, however this produced the same result:
var postProxy = mapper.Map(postDTO, _myDbContext.Posts.Create(), typeof(PostDTO), typeof(Post));
var postWithKeywordsProxy = mapper.Map(postWithKeywordsDTO, dbContext.Posts.Create(), typeof(PostWithKeywordsDTO), typeof(Post));
Also note that if I don't use the proxy versions of the Post class, everything works as expected, so it doesn't appear to be an issue w/ the mapping configuration.
As for possible workarounds, the closest I've found is in this thread (Automapper : mapping issue with inheritance and abstract base class on collections with Entity Framework 4 Proxy Pocos), which appears to be a similar issue, however the workaround in this case was to use the 'DynamicMap' function, which has since been deprecated in AutoMapper. Has anyone else encountered a similar issue w/ proxy class mapping and know of another solution?
Post
and you'll see. I don't know about the EF part. – Lucian Bargaoanu