7

I am using Automapper to map between Entity and ViewModel object (in both directions). The model uses EF4 DbContext POCOs and needs LazyLoading (and therefore Proxy Generation) enabled.

I have come across a problem attempting to update an existing entity from a viewmodel. When I call Mapper.Map(vm, entity), Automapper throws an exception. My question is: how are you supposed to work with EF Proxy objects using Automapper?

The code looks (simplified) like this:

public class MyEntity
{
    public int Id {get;set;}
    public int Name {get;set;}
}

public class ViewModel
{
    public int Id {get;set;}
    public int Name {get;set;}
}

Mapper.CreateMap<MyEntity, ViewModel>();
Mapper.CreateMap<ViewModel, MyEntity>();

public ActionResult Edit(ViewModel vm)
{
    MyEntity entity = db.MyEntities.Find(vm.Id);
    Mapper.Map(vm, entity);
    db.Entry(entity).State = EntityState.Modified;
    db.SaveChanges();
}

When I call Mapper.Map(vm, entity) to update the existing entity object, I get the exception:

'Mapper.Map(viewModel, resultSet)' threw an exception of type 'AutoMapper.AutoMapperMappingException'
base {System.Exception}: {"Missing type map configuration or unsupported mapping.\n\nMapping types:\r\nResultSetView -> ResultSet_692D75838D4DC59B922F3E88CF1B10516CBF6CD8A32C4BE2F3FCC28CE83F0BD2\r\nSciensus.Applications.ClinicalStudies.Web.Areas.Patient.Models.ResultSetView -> System.Data.Entity.DynamicProxies.ResultSet_692D75838D4DC59B922F3E88CF1B10516CBF6CD8A32C4BE2F3FCC28CE83F0BD2\n\nDestination path:\nResultSet_692D75838D4DC59B922F3E88CF1B10516CBF6CD8A32C4BE2F3FCC28CE83F0BD2\n\nSource value:\nSciensus.Applications.ClinicalStudies.Web.Areas.Patient.Models.ResultSetView"}
Context: {Trying to map ResultSetView to ResultSet_692D75838D4DC59B922F3E88CF1B10516CBF6CD8A32C4BE2F3FCC28CE83F0BD2.}
Message: "Missing type map configuration or unsupported mapping.\n\nMapping types:\r\nResultSetView -> ResultSet_692D75838D4DC59B922F3E88CF1B10516CBF6CD8A32C4BE2F3FCC28CE83F0BD2\r\nSciensus.Applications.ClinicalStudies.Web.Areas.Patient.Models.ResultSetView -> System.Data.Entity.DynamicProxies.ResultSet_692D75838D4DC59B922F3E88CF1B10516CBF6CD8A32C4BE2F3FCC28CE83F0BD2\n\nDestination path:\nResultSet_692D75838D4DC59B922F3E88CF1B10516CBF6CD8A32C4BE2F3FCC28CE83F0BD2\n\nSource value:\nSciensus.Applications.ClinicalStudies.Web.Areas.Patient.Models.ResultSetView"
StackTrace: ""
Paul Taylor
  • 5,651
  • 5
  • 44
  • 68

3 Answers3

6

I looked at the AutoMapper source Code:

    public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
    {
        return Map(source, destination, opts => { });
    }

    public TDestination Map<TSource, TDestination>(TSource source, TDestination destination, Action<IMappingOperationOptions> opts)
    {
        Type modelType = typeof(TSource);
        Type destinationType = (Equals(destination, default(TDestination)) ? typeof(TDestination) : destination.GetType());

        return (TDestination)Map(source, destination, modelType, destinationType, opts);
    }

Problems occurred in this place:

  Type destinationType = (Equals(destination, default(TDestination)) ? typeof(TDestination) : destination.GetType());

So the change that do not have a problem:

  Mapper.Map(vm, entity,typeof(ViewModel),typeof(MyEntity));
Root Zhou
  • 76
  • 3
  • Thanks, this works. The marked answer for this question is not really an answer because it just says, go back to doing it manually. This answer should have been the answer to the question in my opinion. The question was how to update an EF proxy object using AutoMapper. – Tyrel Van Niekerk Feb 16 '13 at 14:58
  • I bumped into the same issue. The mentioned solution will works with AutoMapper version 2.0.0.0, and not above. see: http://www.nopcommerce.com/boards/t/19897/urgent-issue-with-admin-mapping.aspx – Roi Shabtai Feb 20 '13 at 15:27
  • 1
    With AutoMapper 2.2, I get "Cannot implicitly convert type 'object' to ''" for this work-around. Maybe @YoYo has the same problem, but it is not explicitly mentioned. – R. Schreurs Apr 26 '13 at 09:36
  • @R.Schreurs I had this problem as well. As I mentioned I use 2 AutoMapper version over 2.2. Version 2 is good enough for me, and perform all the mapping tasks I need. – Roi Shabtai May 01 '13 at 06:47
3

As you suspect, I believe you are getting this exception, because AutoMapper doesn't have a map for the proxy class created by Lazy Loading (ResultSet_692D75838D4DC59B922F3E88CF1B10516CBF6CD8A32C4BE2F3FCC28CE83F0BD2) which derives from your ResultSet entity.

What you might try is the following:

public ActionResult Edit(ViewModel vm)
{
    // This returns the entity proxy
    MyEntity oldEntity = db.MyEntities.Find(vm.Id);
    // i.e. Create a 'plain' Entity, not a proxy.
    MyEntity newEntity = Mapper.Map<ViewModel, MyEntity>(vm);

    db.Entry(oldEntity).CurrentValues.SetValues(newEntity);
    // I don't think you need this now.
    // db.Entry(entity).State = EntityState.Modified;
    db.SaveChanges();
}
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • 1
    Thanks @StuartLC. This looks like a handy workaround. My scenario is a bit more complex because it is rows in a collection property of the main entity that are being updated, but will adapt your approach and test. – Paul Taylor Nov 16 '12 at 14:38
  • Looking at it again, unfortunately, it's not going to work. The problem with using CurrentValues.SetValues is that it will update all the properties on the target entity. As the viewmodel doesn't contain all the properties, newEntity will be partially populated, and will overwrite values on oldEntity with nulls or other invalid values. – Paul Taylor Nov 16 '12 at 14:52
  • @PaulTaylor good point. In your original code, if you replace `Mapper.Map(vm, entity);` with the typed generic `Mapper.Map(vm, entity)`, possibly AM will play ball ? – StuartLC Nov 16 '12 at 14:58
  • Unfortunately not. Both methods appear to be identical (I guess the framework will call the typed overload even when the untyped one is called after inspecting the arguments). I have also tried `entity = Mapper.Map(vm, entity);` but to no avail. – Paul Taylor Nov 16 '12 at 15:24
  • @PaulTaylor Have a look [here](http://stackoverflow.com/questions/3441916/automapper-mapping-issue-with-inheritance-and-abstract-base-class-on-collectio) - it seems to be a common issue with AM and EF proxies. subkamran has a workaround (but not really generic) and chrislhardin seems to give hope that AM will support dynamic proxies. Um, otherwise, how do you feel about ditching the proxies? Eager loading an option? – StuartLC Nov 16 '12 at 15:35
  • I have come to the same conclusion: that there is not a decent workaround for this. Starting to look like a bug in AM. Interestingly I can't find a way of converting a Proxy to an Entity either. For that reason, I am working to the approach of switching off proxies for the queries in question. Unfortunately am hitting another Automapper problem, that properties configured to Ignore() are getting altered during the mapping. I have yet to find a good explanation about how Automapper is supposed to be used in update scenarios. – Paul Taylor Nov 16 '12 at 16:39
  • 1
    Thinking that I will drop Automapper and do the mapping myself, using an IMappable interface. – Paul Taylor Nov 17 '12 at 09:43
1

So what I ended up doing was rolling my own IMappable interface and a simple generic mapping utility to support two-way mapping. Depending on the complexity of the mapping, the code required can end up being less than with Automapper. Code below:

public interface IMappable<TEntity, TViewModel>
{
    void Map(TEntity source, TViewModel target);
    void Map(TViewModel source, TEntity target);
}

public class ModelMapper<TEntity, TViewModel> where TEntity : new() where TViewModel : IMappable<TEntity, TViewModel>, new()
{
    public static TViewModel Map(TEntity source)
    {
        TViewModel target = new TViewModel();
        Map(source, target);
        return target;
    }

    public static TEntity Map(TViewModel source)
    {
        TEntity target = new TEntity();
        Map(source, target);
        return target;
    }

    public static void Map(TEntity source, TViewModel target)
    {
        new TViewModel().Map(source, target);
    }

    public static void Map(TViewModel source, TEntity target)
    {
        new TViewModel().Map(source, target);
    }
}

My ViewModels implement IMappable for as many Entity classes as necessary, implementing a Map method to handle data transfer in each direction:

public class AddressView : IMappable<Address, AddressView>
{
    public long AddressId { get; set; }
    ...

    #region "IMappable members"
    public void Map(Address source, AddressView target)
    {
        target.AddressId = source.AddressId;
        target.City = source.City;
        target.FullAddress = source.FullAddress;
        if (source.CountryId.HasValue)
        {
            target.Country = source.Country.Name;
            target.CountryId = source.CountryId;
        }
        target.District = source.District;
        target.Postcode = source.Postcode;
        target.StreetName = source.StreetName;
    }

    public void Map(AddressView source, Address target)
    {
        target.AddressId = source.AddressId;
        target.City = source.City;
        target.CountryId = source.CountryId;
        target.District = source.District;
        target.Postcode = source.Postcode;
        target.StreetName = source.StreetName;
    }
    #endregion
}
Paul Taylor
  • 5,651
  • 5
  • 44
  • 68