7

Sto utilizzando Automapper per eseguire il mapping tra l'oggetto Entity e ViewModel (in entrambe le direzioni). Il modello utilizza EF4 DbContext POCOs e richiede LazyLoading (e quindi Proxy Generation) abilitato.Come aggiornare un'entità esistente da ViewModel utilizzando Automapper e EF4 DbContext con il caricamento lento abilitato

Mi sono imbattuto in un problema nel tentativo di aggiornare un'entità esistente da un viewmodel. Quando chiamo Mapper.Map (vm, entity), Automapper genera un'eccezione. La mia domanda è: come si dovrebbe lavorare con gli oggetti Proxy EF usando Automapper?

Gli sguardi di codice (semplificato) come questo:

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

Quando chiamo Mapper.Map (vm, entità) per aggiornare l'oggetto entità esistente, ottengo l'eccezione:

'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: "" 
+1

Questo problema è stato risolto in v2.2.1 –

risposta

6

Ho guardato il codice sorgente automapper:

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

I problemi si sono verificati in questo luogo:

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

Così il cambiamento che non hanno un problema:

Mapper.Map(vm, entity,typeof(ViewModel),typeof(MyEntity)); 
+0

Grazie, questo funziona. La risposta marcata per questa domanda non è davvero una risposta perché dice semplicemente, tornare a farlo manualmente. Questa risposta avrebbe dovuto essere la risposta alla domanda secondo me. La domanda era come aggiornare un oggetto proxy EF usando AutoMapper. –

+0

Mi sono imbattuto nello stesso problema. La soluzione menzionata funzionerà con AutoMapper versione 2.0.0.0 e non sopra. vedere: http://www.nopcommerce.com/boards/t/19897/urgent-issue-with-admin-mapping.aspx –

+1

Con AutoMapper 2.2, ottengo "Impossibile convertire in modo implicito" l'oggetto "" a "" "per questo work-around. Forse @YoYo ha lo stesso problema, ma non è menzionato esplicitamente. –

3

Come sospetti, credo che stai ricevendo questa eccezione, perché AutoMapper non ha una mappa per la classe proxy creata da Lazy Loading (ResultSet_692D75838D4DC59B922F3E88CF1B10516CBF6CD8A32C4BE2F3FCC28CE83F0BD2) che deriva dall'entità ResultSet.

Che cosa si potrebbe provare è il seguente:

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(); 
} 
+1

Grazie @StuartLC. Questo sembra un trucco pratico. Il mio scenario è un po 'più complesso perché sono le righe in una proprietà di raccolta dell'entità principale che vengono aggiornate, ma adatteranno il tuo approccio e test. –

+0

Guardando di nuovo, sfortunatamente, non funzionerà. Il problema con l'utilizzo di CurrentValues.SetValues ​​è che aggiornerà tutte le proprietà sull'entità di destinazione. Poiché viewmodel non contiene tutte le proprietà, newEntity verrà popolato parzialmente e sovrascriverà i valori su oldEntity con valori null o altri valori non validi. –

+0

@PaulTaylor buon punto. Nel tuo codice originale, se sostituisci 'Mapper.Map (vm, entity);' con il generico 'Mapper.Map (vm, entity)', probabilmente AM giocherà a palla? – StuartLC

1

Quindi quello che ho finito per fare stava rotolando mia propria interfaccia IMappable e una semplice utility generica mappatura per supportare la mappatura a due vie. A seconda della complessità della mappatura, il codice richiesto può risultare inferiore rispetto a Automapper. Codice qui sotto:

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

miei ViewModels implementare IMappable a molte classi di entità, se necessario, l'attuazione di un metodo di mappa per gestire il trasferimento dei dati in ogni direzione:

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 
}