Automapper: проблема отображения с наследованием и абстрактным базовым классом в коллекциях с Entity Framework 4 Proxy Pocos

У меня возникла проблема с использованием AutoMapper (которая является отличной технологией) для сопоставления бизнес-объекта с DTO, где у меня есть наследство от абстрактного базового класса внутри коллекции.

Вот мои объекты:

abstract class Payment
class CashPayment : Payment
class CreditCardPayment : Payment

У меня также есть объект счета, который содержит коллекцию платежей, например:

    public class Invoice
    {
       ... properties...

       public ICollection<Payment> Payments { get; set; }
    }

У меня также есть соответствующие версии DTO каждого из этих объектов.

Объект DtoInvoice определяется как:

[DataContract]
public class DtoInvoice
{
   ...properties...

   [DataMember]
   public List<DtoPayment> Payments { get; set; }
}

Вот как выглядят мои определения Mapper:

Mapper.CreateMap<Invoice, DtoInvoice>();

Mapper.CreateMap<Payment, DtoPayment>()
  .Include<CashPayment, DtoCashPayment>()
  .Include<CreditCardPayment, DtoCreditCardPayment>();

Mapper.CreateMap<CashPayment, DtoCashPayment>();
Mapper.CreateMap<CreditCardPayment, DtoCreditCardPayment>();

Код для выполнения отображения выглядит следующим образом:

var invoice = repo.GetInvoice(invoiceId);

var dtoInvoice = Mapper.Map<Invoice, DtoInvoice>(invoice);

Так, например, если мой объект счета содержит коллекцию конкретных платежей (например, 1 наличные и 1 кредитную карту), когда картограф пытается их сопоставить, я получаю сообщение об ошибке, которую не может быть создан абстрактный класс Payment. Если я удалю ключевое слово abstract из объекта "Оплата", тогда код работает, но я получаю только коллекцию объекта "Платеж", я не получаю их конкретные объекты (оплата наличными и кредитной картой).

Итак, вопрос: как я могу заставить AutoMapper отображать конкретные типы платежей, а не базовый класс?


Обновление

Я сделал еще несколько копаний и думаю, что вижу проблему, но не уверен, как я могу решить эту проблему с помощью AutoMapper. Я думаю, что это скорее вещь EF, а не ошибка AutoMapper.: -)

В моем коде я использую Entity Framework 4 Proxy POCOs с ленивой загрузкой.

Поэтому, когда я пытаюсь отобразить объект, возвращенный из EF, который является прокси-сервером POCO, он получает этот забавный вид типа:

System.Data.Entity.DynamicProxies.CashPayment_86783D165755C316A2F58A4343EEC4842907C5539AF24F0E64AEF498B15105C2

Итак, моя теория заключается в том, что, когда AutoMapper пытается сопоставить CashPayment с DtoCashPayment, а полученный платеж является прокси-типом, AutoMapper видит его как "несоответствие", а затем отображает общий тип платежа. Но так как оплата представляет собой абстрактные классы автомапперных бомб с помощью "System.InvalidOperationException: экземпляры абстрактных классов не могут быть созданы". исключение.

Итак, вопрос: есть ли способ использовать AutoMapper для сопоставления прокси-объектов EF POCO с Dtos.

Ответ 1

Этот ответ приходит "немного", потому что я столкнулся с той же проблемой с прокси-серверами EF4 POCO.

Я решил это с помощью настраиваемого конвертера, который вызывает Mapper.DynamicMap<TDestination>(object source), чтобы вызвать преобразование типа времени выполнения, а не .Include<TOtherSource, TOtherDestinatio>().

Это отлично работает для меня.

В вашем случае вы определяете следующий конвертер:

class PaymentConverter : ITypeConverter<Payment, DtoPayment> {
    public DtoPayment Convert( ResolutionContext context ) {
        return Mapper.DynamicMap<DtoPayment>( context.SourceValue );
    }
}

И затем:

Mapper.CreateMap<Payment, DtoPayment>().ConvertUsing<PaymentConverter>();
Mapper.CreateMap<CashPayment, DtoCashPayment>();
Mapper.CreateMap<CreditCardPayment, DtoCreditCardPayment>();

Ответ 2

Я также попробовал пример Оливье и получил те же ошибки StackOverflow. Я также попробовал решение подкамера, но не повезло там, поскольку я не использую базовый класс из поколения кода модели сущности. Automapper все еще взрывается. Пока я не найду лучшее решение, я просто установил контекст, чтобы не создавать Proxies, когда я создаю объект Context.

model.Configuration.ProxyCreationEnabled = false; 
model.Configuration.LazyLoadingEnabled = true; 

Я также хотел бы увидеть ответ на проблему, возможно, используя что-то, встроенное в Automapper...

UPDATE: предварительная версия Automapper исправляет эту проблему и позволяет отображать покрытие DynamicProxy без дополнительной настройки.

Релиз, в котором он работает, равен 2.2.1

Ответ 3

Основываясь на реакции Оливье, я не мог заставить его работать в моем контексте... он продолжал идти в бесконечном цикле и бросал исключение StackOverflowException.

В этом примере AbstractClass - это мой базовый класс, а AbstractViewModel - моя базовая модель (не отмечена как abstract).

Тем не менее, я получил его для работы с помощью этого конвертера, выглядящего в стиле хакерства:

    public class ProxyConverter<TSource, TDestination> : ITypeConverter<TSource, TDestination>
        where TSource : class
        where TDestination : class
    {
        public TDestination Convert(ResolutionContext context)
        {
            // Get dynamic proxy base type
            var baseType = context.SourceValue.GetType().BaseType;

            // Return regular map if base type == Abstract base type
            if (baseType == typeof(TSource))
                baseType = context.SourceValue.GetType();

            // Look up map for base type
            var destType = (from maps in Mapper.GetAllTypeMaps()
                           where maps.SourceType == baseType
                           select maps).FirstOrDefault().DestinationType;

            return Mapper.DynamicMap(context.SourceValue, baseType, destType) as TDestination;
        }
    }

    // Usage

    Mapper.CreateMap<AbstractClass, AbstractViewModel>()
        .ConvertUsing(new ProxyConverter<AbstractClass, AbstractViewModel>());

Итак, a DerivedClassA будет отображаться нормально, но a DynamicProxy_xxx также будет правильно отображаться, так как этот код проверяет его базовый тип (DerivedClassA).

Пожалуйста, пожалуйста, пожалуйста, покажите мне, что мне не нужно делать это безумное дерьмо. Я не знаю достаточно AutoMapper для правильного ответа Оливье.

Ответ 4

Я столкнулся с той же проблемой с прокси-серверами Entity Framework, но не хотел переключиться на предварительную версию AutoMapper. Я нашел простую, слегка уродливую работу для версии 2.2.0. Я пытался перейти от DTO к существующему прокси-объекту EF и получал ошибки в отношении отсутствия сопоставления для имени уродливого прокси-класса. Моим решением было использовать перегрузку указанных конкретных конкретных типов, которые я вручную сопоставил:

Mapper.Map(dtoSource, entityDest, typeof(DtoClass), typeof(ConcreteEntityClass));

Ответ 5

Я столкнулся с той же проблемой с отображением динамических прокси-серверов EF в ViewModels в приложении MVC.

Я нашел простое решение, используя Mapper.DynamicMap() для этой проблемы. Вот мой код:

Преобразование из динамического прокси в класс ViewModel:

// dynamic proxy instance
WebService webService = _repWebService.GetAll().SingleOrDefault(x => x.Id == id);

//mapping
FirstStepWebServiceModel model = Mapper.DynamicMap<FirstStepWebServiceModel>(webService);

Преобразование из класса ViewModel в динамический прокси EF:

[HttpPost]
public ActionResult FirstStep(FirstStepWebServiceModel input)
{
    // getting the dynamic proxy from database
    WebService webService = _repWebService.GetAll().Single(x => x.Id == input.WebServiceId);

    // mapping the input ViewModel class to the Dynamic Proxy entity
    Mapper.DynamicMap(input, webService);
}

Надеюсь, что этот пример поможет вам