Automapper: problem z mapowaniem z dziedziczeniem i abstrakcyjną klasą bazową na kolekcjach z Entity Framework 4 Proxy Pocos

Mam problem z wykorzystaniem AutoMapper (która jest doskonałą technologią) do mapowania obiektu biznesowego do DTO, gdzie mam dziedziczenie z abstrakcyjnej klasy bazowej w kolekcji.

Oto moje obiekty:

abstract class Payment
class CashPayment : Payment
class CreditCardPayment : Payment

Mam również obiekt faktury, który zawiera zbiór płatności w ten sposób:

    public class Invoice
    {
       ... properties...

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

Mam również odpowiednie wersje DTO każdego z tych obiektów.

Obiekt DtoInvoice jest zdefiniowany jako:

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

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

Oto co mój maper definicje wyglądają następująco:

Mapper.CreateMap<Invoice, DtoInvoice>();

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

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

Kod do wykonania mapowania wygląda następująco:

var invoice = repo.GetInvoice(invoiceId);

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

Więc na przykład, jeśli mój obiekt faktury zawiera zbiór określonych płatności (powiedzmy 1 Gotówka i 1 karta kredytowa), gdy mapper próbuje je zmapować, dostaję błąd, że nie można utworzyć abstrakcyjnej płatności klasy. Jeśli usunę abstrakcyjne słowo kluczowe z obiektu płatności, kod działa, ale dostaję tylko zbiór obiektu płatności, nie dostaję ich konkretnych obiektów (gotówka & karta kredytowa płatności).

Więc pytanie brzmi: Jak mogę zmusić Automapp do mapowania konkretnych typów płatności, a nie klasy bazowej?


Update

Trochę poszperałem i myślę, że widzę problem, ale nie jestem pewien, jak mogę rozwiązać ten problem z AutoMapper. Myślę, że to bardziej sprawa EF, a nie wina Automapera. :-)

W moim kodzie używam Entity Framework 4 Proxy POCOs z leniwym ładowaniem.

Więc kiedy próbuję odwzorować encję zwróconą z EF, która jest proxy POCO robi się taki śmieszny typ jak:

System.Data.Entity.DynamicProxies.CashPayment_86783D165755C316A2F58A4343EEC4842907C5539AF24F0E64AEF498B15105C2

Więc moja teoria jest taka, że gdy AutoMapper próbuje mapować CashPayment do DtoCashPayment, a płatność przekazywana jest typu proxy, AutoMapper widzi to jako "non match", a następnie mapuje ogólny typ płatności. Ale ponieważ płatność jest abstrakcyjna klasy AutoMapper bomby z " systemu.InvalidOperationException: nie można utworzyć instancji klas abstrakcyjnych."wyjątek.

Więc pytanie brzmi: Czy Jest jakiś sposób dla mnie, aby użyć AutoMapper do mapowania obiektów proxy EF POCO do Dtos.

Author: Ken Burkhardt, 2010-08-09

5 answers

Ta odpowiedź przychodzi "trochę" późno, ponieważ właśnie spotkałem się z tym samym problemem z proxy EF4 POCO.

Rozwiązałem to za pomocą niestandardowego konwertera, który wywołuje Mapper.DynamicMap<TDestination>(object source), aby wywołać konwersję typu runtime, a nie .Include<TOtherSource, TOtherDestinatio>().

Dla mnie działa dobrze.

W Twoim przypadku zdefiniowałbyś następujący konwerter:

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

A następnie:

Mapper.CreateMap<Payment, DtoPayment>().ConvertUsing<PaymentConverter>();
Mapper.CreateMap<CashPayment, DtoCashPayment>();
Mapper.CreateMap<CreditCardPayment, DtoCreditCardPayment>();
 15
Author: oli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-23 11:26:19

Wypróbowałem również przykład Oliviera i dostałem te same błędy StackOverflow. Próbowałem również rozwiązania subkamrana, ale nie miałem szczęścia, ponieważ nie używam klasy bazowej z generacji kodu modelu encji. Automaper wciąż wybucha. Dopóki nie znajdę lepszego rozwiązania, po prostu ustawiam kontekst, aby nie tworzyć proxy podczas tworzenia obiektu kontekstowego.

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

Chciałbym również zobaczyć odpowiedź na problem, być może używając czegoś wbudowanego w Automapper...

UPDATE: The Pre-release of Automapper naprawia ten problem i pozwala na mapowanie na pokrycie DynamicProxy bez dodatkowej konfiguracji.

Wersja, w której to działa, to 2.2.1

 15
Author: chrislhardin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-13 00:10:05

Opierając się na odpowiedzi Oliviera, nie mogłem zmusić go do pracy w moim kontekście... ciągnął się w nieskończonej pętli i rzucał StackOverflowException.

W tym przykładzie, AbstractClass jest moją klasą bazową, a {[2] } jest moim podstawowym modelem widoku (nie oznaczonym jako abstract pamiętaj o tym).

Jednak udało mi się go uruchomić używając tego hakerskiego konwertera: {]}

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

Więc {[4] } będzie mapować normalnie, ale DynamicProxy_xxx będzie również mapować poprawnie, ponieważ ten kod sprawdza swój typ bazowy (DerivedClassA).

Proszę, Proszę, proszę, Pokaż mi, że nie muszę robić tego szalonego gówna wyszukiwania. Nie znam wystarczająco dużo Automapera, aby poprawnie naprawić odpowiedź Oliviera.

 14
Author: kamranicus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-21 20:53:42

Napotkałem ten sam problem z serwerami proxy Entity Framework, ale nie chciałem przełączyć się na przedpremierową wersję Automappera. Znalazłem prostą, jeśli nieco brzydką pracę dla wersji 2.2.0. Próbowałem przejść z DTO do istniejącego obiektu proxy EF i otrzymywałem Błędy o pominięciu mapowania dla brzydkiej nazwy klasy proxy. Moim rozwiązaniem było użycie przeciążenia określonych rzeczywistych typów betonu, które ręcznie mapowałem:

Mapper.Map(dtoSource, entityDest, typeof(DtoClass), typeof(ConcreteEntityClass));
 10
Author: davesw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-21 22:09:28

Właśnie napotkałem ten sam problem z mapowaniem dynamicznych proxy EF do ViewModels w aplikacji MVC.

Znalazłem łatwe rozwiązanie używając Mapper.DynamicMap () dla tego problemu. Oto Mój kod:

Konwersja z dynamicznego proxy do klasy ViewModel:

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

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

Konwersja z klasy ViewModel do dynamicznego Proxy 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);
}

Mam nadzieję, że ten przykład ci pomoże

 6
Author: Ilya Schukin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-17 10:08:19