Automapper : mapping issue with inheritance and abstract base class on collections with Entity Framework 4 Proxy Pocos

23,190

Solution 1

This answer comes 'a bit' late as I've just faced the same issue with EF4 POCO proxies.

I solved it using a custom converter that calls Mapper.DynamicMap<TDestination>(object source) to invoke the runtime type conversion, rather than the .Include<TOtherSource, TOtherDestinatio>().

It works fine for me.

In your case you would define the following converter:

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

And then:

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

Solution 2

I also tried Olivier's example and got the same StackOverflow errors. I also tried subkamran's solution but not luck there as I am not using a base class from the entity model code generation. Automapper still blows up. Until I find a better solution, I just set the Context to not create Proxies when I create a Context object.

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

I would also like to see an answer to the problem perhaps using something build into Automapper...

UPDATE: The Pre-release of Automapper corrects this issue and allows for the mapping to cover a DynamicProxy with no extra configuration.

The release this works in is 2.2.1

Solution 3

Building on Olivier's response, I could not get his to work in my context... it kept going in an infinite loop and threw a StackOverflowException.

In this example, AbstractClass is my base class and AbstractViewModel is my base view model (not marked as abstract mind you).

However, I did get it to work using this hackish looking converter:

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

So, a DerivedClassA will map normally, but a DynamicProxy_xxx will also map properly as this code inspects its base type (DerivedClassA).

Please, please, please show me that I don't have to do this crazy lookup crap. I don't know enough AutoMapper to fix Olivier's answer properly.

Solution 4

I ran into the same issue with Entity Framework proxies, but didn't want to switch to a pre-release version of AutoMapper. I found a simple if slightly ugly work around for version 2.2.0. I was trying to go from a DTO to an existing EF proxy object, and was getting errors about missing a mapping for the ugly proxy class name. My solution was to use an overload the specified the actual concrete types that I'd manually mapped:

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

Solution 5

I've just faced the same problem with mapping dynamic EF proxies to ViewModels in MVC application.

I found an easy solution using Mapper.DynamicMap() for this problem. Here is my code:

Converting from Dynamic proxy to ViewModel class:

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

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

Converting from ViewModel class to EF Dynamic Proxy:

[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);
}

Hope this example help you

Share:
23,190
Ken Burkhardt
Author by

Ken Burkhardt

I am a consulting manager for the Adobe Marketing Cloud. I am a technical leader with over 20+ years of professional experience in software development, engineering, mentoring and agile practices.

Updated on June 18, 2020

Comments

  • Ken Burkhardt
    Ken Burkhardt almost 4 years

    I am having an issue using AutoMapper (which is an excellent technology) to map a business object to a DTO where I have inheritance off of an abstract base class within a collection.

    Here are my objects:

    abstract class Payment
    class CashPayment : Payment
    class CreditCardPayment : Payment
    

    I also have an invoice object which contains a collection of payments like so:

        public class Invoice
        {
           ... properties...
    
           public ICollection<Payment> Payments { get; set; }
        }
    

    I also have corresponding DTO versions of each of these objects.

    The DtoInvoice object is defined as:

    [DataContract]
    public class DtoInvoice
    {
       ...properties...
    
       [DataMember]
       public List<DtoPayment> Payments { get; set; }
    }
    

    This is what my Mapper definitions look like:

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

    The code to perform the mapping looks like this:

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

    So for example if my invoice object contains a collection of specific payments (say 1 cash and 1 credit card) when mapper tries to map them I get an error that the abstract class Payment cannot be created. If I remove the abstract keyword from the Payment object then the code works but I only get a collection of Payment object, I do not get their specific objects (Cash & Credit Card payments).

    So the question is: How can I get AutoMapper to map the specific payment types and not the base class?


    Update

    I did some more digging and think I see a problem but am not sure how I can solve this with AutoMapper. I think this is more of an EF thing and not AutoMapper's fault. :-)

    In my code I am using Entity Framework 4 Proxy POCOs with lazy loading.

    So when I try to map an entity returned from EF that is a proxy POCO it gets that funny looking type like:

    System.Data.Entity.DynamicProxies.CashPayment_86783D165755C316A2F58A4343EEC4842907C5539AF24F0E64AEF498B15105C2
    

    So my theory is that when AutoMapper tries to map CashPayment to DtoCashPayment and the payment passed in is of the proxy type AutoMapper sees it as a "non match" and then maps the generic Payment type. But since Payment is an abstract class AutoMapper bombs with a "System.InvalidOperationException: Instances of abstract classes cannot be created." exception.

    So the question is: Is there a way for me to use AutoMapper to map EF POCO proxy objects to Dtos.

  • Richard B
    Richard B about 12 years
    bump has anyone found out how to prevent the stackoverflowexception?
  • daveaglick
    daveaglick over 11 years
    Thanks for pointing out the pre-release does this out of the box, saved my day as well.
  • chrislhardin
    chrislhardin over 11 years
    You're most welcome guys. I pulled my hair out for a couple days until I found it. I wanted to save someone else the trouble.
  • justabuzz
    justabuzz over 11 years
    Great! Saved me a lot of time + I can use this while waiting for 2.2.1 to release. Now we need to hope that it's be released quickly! :)
  • MattSlay
    MattSlay over 11 years
    Bingo!! Works like a charm.
  • bloparod
    bloparod about 11 years
    I created the map with Mapper.CreateMap<...> and used Mapper.DynamicMap<...>() to execute the mapping, and it worked. I didn't have to create a custom resolver.
  • ms007
    ms007 almost 11 years
    In AutoMapper (FinalRelease 2.2.1) I still get the StackOverflowException using Olivers solution. Am I missing something?
  • StefanG
    StefanG about 9 years
    This should be the solution. Works also for Entity Framework 6.
  • Jack
    Jack over 7 years
    Thanks but not worked for AutoMapper version 5.x.x.x. Could you please have a look at Using AutoMapper to map Base Classes question?
  • Jack
    Jack over 7 years
    Thanks but not worked for me. Could you please have a look at Using AutoMapper to map Base Classes question?
  • Jack
    Jack over 7 years
    Thanks but not worked for me. Could you please have a look at Using AutoMapper to map Base Classes question?
  • John Mc
    John Mc over 7 years
    Where are you getting DynamicMap method from? I can't see it in the source code for Automapper (github.com/AutoMapper/AutoMapper/tree/master/src/AutoMapper‌​)
  • Daniel Leiszen
    Daniel Leiszen over 7 years
    It has been removed since v5.0 i think. They say it is merged into the Map method, however I could not make it work with the Map method above v5 so I had to rollback to v4.x. This whole thing is a mess. I have already regretted to use automapper. I have inheritance, nested objects and proxies at the same time and I cannot make it work with automapper. It causes more problems it solves. I could have mapped the properties one by one for all my classes along the time I spent looking for workarounds.
  • Mihai Alexandru-Ionut
    Mihai Alexandru-Ionut about 7 years
    @chrislhardin, saved my day!