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

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

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

abstract class Payment
class CashPayment : Payment
class CreditCardPayment : Payment

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

    public class Invoice
    {
       ... properties...

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

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

определен объект DtoInvoice as:

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

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

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

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 кредитная карта), когда mapper пытается сопоставить их, я получаю ошибку, что абстрактный класс оплаты не может быть создан. Если я удаляю ключевое слово abstract из объекта Payment, код работает, но я получаю только коллекцию Объект оплаты, я не получаю их конкретные объекты (наличные и платежи по кредитным картам).

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


обновление

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

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

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

System.Data.Entity.DynamicProxies.CashPayment_86783D165755C316A2F58A4343EEC4842907C5539AF24F0E64AEF498B15105C2

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

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

5 ответов


этот ответ приходит "немного" поздно, так как я только что столкнулся с той же проблемой с прокси-серверами 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>();

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

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

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

обновление: Предварительный выпуск Automapper исправляет эту проблему и позволяет сопоставлению покрывать DynamicProxy без дополнительной конфигурации.

релиз это работает в 2.2.1


основываясь на ответе Оливье, я не мог заставить его работать в моем контексте... он продолжал идти в бесконечном цикле и бросил 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 будет отображаться нормально, но DynamicProxy_xxx также будет правильно отображаться, поскольку этот код проверяет его базовый тип (DerivedClassA).

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


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

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

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

Я нашел простое решение, используя маппер.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);
}

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