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);
}
Надеюсь, что этот пример поможет вам