AutoMapper Entity Framework Proxy Унаследованная проблема отображения классов - PullRequest
0 голосов
/ 24 апреля 2020

Возникла проблема с AutoMapper (v9.0), использующим неправильное сопоставление для унаследованного класса при сопоставлении с прокси-классом Entity Framework (v6.4). Похоже, что это связано с порядком, в котором выполняется отображение, и, похоже, связано с некоторым типом кэширования используемых карт. Вот конфигурация Entity Framwork:

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 с использованием (обратите внимание, мне нужно разрешить создание прокси-класса по соображениям производительности в моем app):

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

получаются прокси-объекты (псевдо- 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 в отображении и ставлю точку останова в строках 'return', я вижу, что сопоставление PostDTO -> Post выполняется используется в обоих случаях, и PostWithKeywords-> Post Mapping не затрагивается вообще.

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 mapping. Затем он сталкивается с попыткой сопоставления между PostWithKeywordsDTO -> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 и не понимает, что PostWithKeywordsDTO на самом деле является дочерним элементом PostDTO, но ошибочно воспринимает * Post * 10, что ошибочно, но ошибочно воспринимает 10 *, что является ошибочным, но по-разному означает *, что означает *, что является ошибкой *, то есть, то есть, то есть, то есть *, то есть, что является ошибкой *, то есть, то есть, то есть *, то есть, то есть ошибочно, но по-разному думает о том, что это не так, как если бы это было *, то есть 10 *, то есть ошибочно, но по-разному думает о том, что делает Post_TO, и ошибочно воспринимает это как 10 *. что произойдет, если я переверну порядок выполнения карт:

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_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 карта бывает, не в состоянии найти кэшированный тип карту, которая удовлетворяет параметры и она продолжает генерировать правильную карту в кэше

Я пытался использовать версию метода 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: проблема с отображением с наследованием и абстрактным базовым классом в коллекциях с Entity Framework 4 Proxy Pocos ), что, похоже, является аналогичной проблемой, однако В этом случае karound должен был использовать функцию DynamicMap, которая с тех пор устарела в AutoMapper. Кто-нибудь еще сталкивался с подобной проблемой с отображением классов прокси и знает другое решение?

1 Ответ

1 голос
/ 25 апреля 2020

Вот что я сделал, чтобы решить проблему. Немного покопавшись в коде, я решил, что мое решение форсировать типы отображения вызовет другие проблемы с наследованием отображения.

Вместо этого я остановился на решении, которое вычисляет «расстояние» каждой сопоставляемой карты типов от запрошенных типов на основе количества уровней наследования, типы источника / назначения карты типов - из соответствующих запрошенных типов, и выбирает «ближайший». Он делает это, обрабатывая «Расстояние до источника» как значение 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 mapping, так как расстояние наименьшее. Похоже, что это работает во всех случаях и удовлетворяет всем модульным тестам AM. Вот гист обновлений, необходимых для кода (хотя я уверен, что, возможно, есть более чистые / более эффективные способы сделать это).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...