AutoMapper实体框架代理继承的类映射问题

时间:2020-04-23 21:17:38

标签: entity-framework proxy automapper

在映射到实体框架(v6.4)代理类时,使用继承类的错误映射遇到了AutoMapper(v9.0)的问题。它似乎与执行映射的顺序有关,并且似乎与所用映射的某种缓存有关。这是实体框架配置:

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

我的DTO课程:

public class PostDTO
{
    public DateTime PostDate { get; set; }
    public string Content { get; set; }
}

public class PostWithKeywordsDTO : PostDTO
{
    public string Keywords { get; set; }
}

映射个人资料:

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

我正在尝试将这些DTO对象映射到'Post'类的代理上,该代理是通过从数据库中获取现有的Post记录或通过使用创建一个Post类的新代理来生成的(注意,我需要出于性能原因在我的应用中启用代理类创建):

_myDbContext.Posts.Create();

现在,当我尝试执行从以下postDTO和postWithKeywordsDTO对象到代理类的映射时:

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

生成的代理对象是(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'
}

此外,如果我在映射中使用内联ValueResolver之类的东西,并在“返回”行上放置断点,则可以看到在两种情况下都使用了PostDTO-> Post映射,并且PostWithKeywords-> Post映射根本没有被击中。

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

我从中得到的是,似乎在处理代理对象时标识使用哪种类型映射存在某种问题。似乎在第一种情况下,它遇到了PostDTO-> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939(代理类)之间的映射尝试,并正确地确定要使用的映射是PostDTO-> Post映射。然后,它在PostWithKeywordsDTO-> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939之间遇到尝试的映射,并且没有意识到PostWithKeywordsDTO实际上是PostDTO的子级,并且错误地重新使用了PostDTO-> Post映射。

但是,奇怪的是,如果我颠倒了映射的执行顺序会发生什么:

var postWithKeywordsProxy = mapper.Map(postWithKeywordsDTO, dbContext.Posts.Create());
var postProxy = mapper.Map(postDTO, _myDbContext.Posts.Create());

生成的代理对象正确:

postWithKeywordsProxy: {
    PostDate: '6/30/2005', 
    Content: 'Post #2', 
    Keywords: 'C#, Automapper, Proxy'
}

postProxy: {
    PostDate: '1/1/2000', 
    Content: 'Post #1', 
    Keywords: 'No Keywords Specified'
}  

这使我认为它与某种缓存机制有关,该缓存机制可能会寻找它可以找到的第一个映射,该映射满足请求的代理映射,即使这不完全匹配。在这种情况下,PostWithKeywordsDTO-> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939映射首先发生,这样当后续的PostDTO-> Post_B24121DF0B309dC6258AA7C620C6D74F4114A743S发生时,生成的Maps缓存不存在,而拥有正确缓存的状态则是>

我确实尝试使用Map方法的版本,该版本接受要映射项目的显式类型,但这产生了相同的结果:

var postProxy = mapper.Map(postDTO, _myDbContext.Posts.Create(), typeof(PostDTO), typeof(Post));
var postWithKeywordsProxy = mapper.Map(postWithKeywordsDTO, dbContext.Posts.Create(), typeof(PostWithKeywordsDTO), typeof(Post));

还请注意,如果我不使用Post类的代理版本,那么一切都会按预期进行,因此与映射配置无关,这似乎不是问题。

关于可能的解决方法,我发现最接近的是该线程(Automapper : mapping issue with inheritance and abstract base class on collections with Entity Framework 4 Proxy Pocos),这似乎是一个类似的问题,但是在这种情况下,解决方法是使用'DynamicMap'函数,该函数此后在AutoMapper中已弃用。有没有其他人遇到过类似的问题/代理类映射,并且知道其他解决方案?

1 个答案:

答案 0 :(得分:1)

这就是我最终要解决的问题。深入研究代码后,我决定强制映射类型的解决方案将导致映射继承的其他问题。

相反,我确定了一个解决方案,该解决方案根据类型映射源/目标类型来自相应请求类型的继承级别数,计算每个匹配类型映射与请求类型的“距离”,然后选择“最接近的”。通过在标准的两个坐标距离计算中将“源距离”作为x值并将“目标距离”作为y值来完成此操作:

Overall Distance = SQRT([Source Distance]^2 + [Destination Distance]^2)

例如,在我的场景中,我有以下地图:

PostDTO -> Post
and
PostWithKeywordsDTO -> Post

在尝试映射PostWithKeywordsDTO-> PostProxy时,没有精确的映射匹配,因此我们必须确定最适合的映射。在这种情况下,可以使用的可能地图列表为:

PostDTO -> Post (Since PostWithKeywordsDTO inherits from PostDTO and PostProxy inherits from Post)
or
PostWithKeywordsDTO -> Post (Since PostProxy inherits from Post)

要确定要使用的地图,它会计算:

PostDTO -> Post: 
Source Distance = 1 (PostDTO is one level above PostWithKeywordsDTO)
Destination Distance = 1 (Post is one level above PostProxy)
Overall Distance = 1.414

PostWithKeywordsDTO -> Post
Source Distance = 0 (since PostWithKeywordsDTO = PostWithKeywordsDTO)
Destination Distance = 1 (Post is one level above PostProxy)
Overall Distance = 1

因此,在这种情况下,由于距离最小,因此将使用PostWithKeywordsDTO-> Post映射。这似乎在所有情况下都有效,并且也满足所有AM单元测试。这里是gist所需要的代码更新(尽管我确信可能有更清洁/更有效的方法来实现)。