2016-02-23 11 views
6

Ho l'oggetti di origine e di destinazione in questo modo:Come utilizzare AutoMapper per mappare l'oggetto destionation con un oggetto figlio nell'oggetto di origine?

class ProductWithCategories // Source class 
{ 
    public Product Product { get; set; } // Product is an EF entity class 
    public IEnumerable<Category> Categories { get; set; } 
} 

class ProductViewModel // Dest class 
{ 
    public int Id { get; set; } 
    // Other properties with the same name as Product class 

    public IEnumerable<CategoryViewModel> Categories { get; set; } 
} 

Quindi, la mia necessità è quella di mappare i valori di source.Product in dest, e poi source.Categories in dest.Categories. È possibile con AutoMapper?

Ho provato questo e non mi ha sorpreso quando non è riuscito:

 config.CreateMap<ProductWithCategories, ProductViewModel>() 
      .ForMember(q => q, option => option.MapFrom(q => q.Product)) 
      .ForMember(q => q.Categories, option => option.MapFrom(q => q.Categories)); 

Qui è l'eccezione che ho ricevuto:

[AutoMapperConfigurationException: Configurazione personalizzata per i soci è supportata solo per il top -level singoli membri su un tipo.]

risposta

11

Dopo alcune discussioni con OP, risulta che il suo bisogno principale è quello di mappare rapidamente un oggetto figlio/nidificato all'interno dell'oggetto sorgente sull'oggetto di destinazione appiattito. Non vuole scrivere una mappatura per ogni proprietà della destinazione.

Ecco un modo per raggiungere questo obiettivo:

  • definire un mapping Product ->ProductViewModel utilizzato per appiattire i membri del prodotto
  • Definire una mappatura Category-CategoryViewModel
  • Definire una mappatura ProductWithCategories - >ProductViewModel che mappa le categorie, quindi nella mappa successiva, mappa il Product:

    config.CreateMap<ProductWithCategories, ProductViewModel>() .ForMember(q => q.Id, option => option.Ignore()) // flattened in AfterMap .ForMember(q => q.Categories, option => option.MapFrom(q => q.Categories)) .AfterMap((src, dst) => Mapper.Map(src.Product, dst));

+0

Esiste un modo per realizzare questo utilizzando i mapper basati su istanze? Questo sta usando il 'Mapper' statico. – kamranicus

+0

Sì, è possibile, ha aggiunto un commento alla risposta di Cliff. Per realizzarlo è necessaria la versione 5.x di Automapper. – kamranicus

0

La riga di errore che genera l'errore è

.ForMember(q => q, option => option.MapFrom(q => q.Product)) 

Il messaggio di errore è difficile da capire, ma significa che bisogna indicare le proprietà di destinazione in modo esplicito:

.ForMember(q => q.Id, option => option.MapFrom(q => q.Product.Id)) 
.ForMember(q => q.OtherProperty, option => option.MapFrom(q => q.Product.OtherProperty)) 

È inoltre necessario definire un mapping Category-CategoryViewModel per

.ForMember(q => q.Categories, option => option.MapFrom(q => q.Categories)) 

al lavoro:

config.CreateMap<Category, CategoryViewModel>(); 
+1

Non so perché l'editor abbia cambiato il titolo della domanda. Penso che il mio titolo originale sia più informativo. Capisco perché ottengo l'errore, il mio bisogno principale è quello di mappare rapidamente un oggetto figlio/nidificato all'interno dell'oggetto sorgente sul mio oggetto di destinazione appiattito. Non è bello se devo specificare ogni proprietà di 'Product', uccide lo scopo di AutoMapper. –

+0

Avvolgere i dati del Prodotto invece di renderli piatti nel ViewModel renderebbe più facile scrivere. Oppure puoi definire una mappatura tra 'Product' e' ProductViewModel' e mappare le categorie in 'AfterMap'. –

0

si dovrebbe fare come -

AutoMapper.Mapper.CreateMap<Category, CategoryViewModel>(); 
AutoMapper.Mapper.CreateMap<ProductWithCategories, ProductViewModel>() 
    .ForMember(a => a.Id, b => b.ResolveUsing(c => c.Product != null ? c.Product.MyProperty : 0)) 
    .ForMember(a => a.Categories, b => b.ResolveUsing(c => c.Categories)); 

Ma è meglio avvolgere quelle proprietà da ProductViewModel (oggetti di scena come Id) all'interno di un'altra classe. E crea un'altra mappa per far funzionare le cose in automapper.

+1

Sì, ci avevo già pensato prima, ma posto questa domanda qui per vedere se c'è un modo per realizzarlo senza creare classi annidate anche nella classe di destinazione. –

+0

sei libero di creare le tue implementazioni per 'TypeConverter' o' ValueResolver'. Ti permettono di controllare effettivamente la creazione del tuo oggetto di destinazione. –

2

Utilizzando le versioni recenti di automapper, si può fare qualcosa di simile al seguente:

config.CreateMap<Product, ProductViewModel>() 
     .ForMember(q => q.Categories, option => option.Ignore()); 

config.CreateMap<ProductWithCategories, ProductViewModel>() 
     .ConstructUsing(s => AutoMapper.Mapper.Map<ProductViewModel>(s.Product)) 
     .ForMember(q => q.Categories, option => option.MapFrom(q => q.Categories)) 
     .ForAllOtherMembers(o => o.Ignore(); 

ConstructUsing() viene utilizzato per generare e popolare la classe di base da parte del bambino nidificato [i] di la fonte. Se si dispone di più di un figlio nidificato di questo tipo, è necessario eseguire più chiamate di mapping per mappare ciascuna di esse successivamente sull'istanza generata dalla prima chiamata Map(). .ForAllOtherMembers() è relativamente recente (se non ce l'hai, ottieni una versione più recente di AutoMapper.) Purtroppo è un po 'pericoloso come se si aggiungessero membri di destinazione che avranno bisogno di mappatura ma si dimentichi di aggiornare la mappa, la convalida della configurazione non prenderla.

+1

Solo per le persone che utilizzano l'inizializzazione basata sull'istanza, passare a '.ConstructUsing ((s, ctx) => ctx.Mapper.Map (s.Product))'. È possibile accedere al mappatore di runtime su ResolutionContext. – kamranicus

Problemi correlati