I have an object (ProductModel) that has a nested list of images. I am trying to simplify the model (Product) that has this list as its property. I am using Automapper, but I can not seem to get the mapping configuration right. I viewed several other posts, but they seem to be a little different than what I am trying to achieve.
// Map to:
public class Product
{
public List<Image> Images { get; set; }
}
public class Image
{
public string url { get; set; }
}
// Map from:
public class ProductModel
{
public ImageSet ImageSet { get; set; }
}
public class ImageSet
{
public List<ImageDetail> ImageDetails { get; set; }
}
public class ImageDetail
{
public string Url { get; set; }
}
The following configuration should work:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ImageDetail, Image>();
cfg.CreateMap<ProductModel, Product>()
.ForMember(dest => dest.Images, opt => opt.MapFrom(src => src.ImageSet.ImageDetails))
;
});
Related
I'm pretty new using AutoMapper and i run into an issue
I have a model like this.
public class StepUp {
public string Example {get;set;}
public string Example2 {get;set;}
public decimal? auxValue { get;set; }
}
But i have two ViewModels as destination
public class NuevoStepUpViewModel()
{
public bool TieneAuxiliar { get; set; }
public string Example { get;set; }
public CargaDatosElectricos CargaDatosElectricos { get; set; }
}
public class CargaDatosElectricos {
public CargaDatosElectricos(bool tieneAuxiliar)
{
TieneAuxiliar = tieneAuxiliar;
}
public readonly bool TieneAuxiliar;
public string Example2 { get; set; }
}
I think some like this:
CreateMap<StepUp,NuevoStepUpViewModel()
.ForMember(x => x.TieneAuxiliar, x => x.MapFrom(c => c.auxValue.HasValue))
.ForMember(x => x.Example, x => x.MapFrom(c => c.Example))
.ForMember(x => x.CargaDatosElectricos.Example2, x => x.MapFrom(c => c.Example2))
.BeforeMap((x,y) => {
x.CargaDatosElectricos = new CargaDatosElectricos(c.auxValue.HasValue);
});
But i'm getting
Expression 'x => x.CargaDatosElectricos.Example2' must resolve to
top-level member and not any child object's properties
How should i create my mapper configuration to do this type of mapping?
There are some errors on your code. You could configure better your mapping using the AfterMap scope instead of BeforeMap to provide a complex configuration. (I am not sure but I think the) AutoMapper will not instance a property where the type is a class. So, you have to do it on the construtor of the destination class (VIewModel) or do it on AfterMap.
The TieneAuxiliar property will not allow you to set a value when it is readonly, so, you will not able to configure a map to this property. I change it to a public classic property.
See the working sample here:
https://dotnetfiddle.net/HSyUVv
using System;
using AutoMapper;
public class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<StepUp, NuevoStepUpViewModel>()
.ForMember(vm => vm.TieneAuxiliar, opt => opt.MapFrom(e => e.auxValue.HasValue))
.ForMember(vm => vm.Example, opt => opt.MapFrom(e => e.Example))
.AfterMap((e, vm) =>
{
vm.CargaDatosElectricos.Example2 = e.Example2;
});
});
var mapper = config.CreateMapper();
var stepUp = new StepUp()
{
Example = "Example 1",
Example2 = "Example 2",
auxValue = 10m
};
var viewModel = mapper.Map<StepUp, NuevoStepUpViewModel>(stepUp);
Console.WriteLine("SteUp was converted to ViewModel");
Console.WriteLine("TieneAuxiliar: {0}", viewModel.TieneAuxiliar);
Console.WriteLine("Example: {0}", viewModel.Example);
Console.WriteLine("CargaDatosElectricos.TieneAuxiliar: {0}", viewModel.CargaDatosElectricos.TieneAuxiliar);
Console.WriteLine("CargaDatosElectricos.Exemple2: {0}", viewModel.CargaDatosElectricos.Example2);
}
public class StepUp
{
public string Example { get; set; }
public string Example2 { get; set; }
public decimal? auxValue { get; set; }
}
public class NuevoStepUpViewModel
{
public bool TieneAuxiliar { get; set; }
public string Example { get;set; }
public CargaDatosElectricos CargaDatosElectricos { get; set; }
public NuevoStepUpViewModel()
{
this.CargaDatosElectricos = new CargaDatosElectricos();
}
}
public class CargaDatosElectricos
{
public CargaDatosElectricos()
{
}
public bool TieneAuxiliar { get; set; }
public string Example2 { get; set; }
}
}
This is my class which holds database data:
public partial class PermissionGroup
{
public int Id { get; set; }
public string Name { get; set; }
// other database properties
public virtual ICollection<GroupActionPermission> GroupActionPermissions { get; set; }
}
And that's my dto's:
public class PermissionGroupDTO
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<GroupActionPermissionDTO> ActionPermissions { get; set; }
}
public class GroupActionPermissionDTO
{
public int Id { get; set; }
public int GroupId { get; set; }
public int PermissionActionId { get; set; }
public PermissionGroupDTO Group { get; set; }
}
Now, I am making mapping:
public IEnumerable<PermissionGroupDTO> GetGroups()
{
return OnConnect<IEnumerable<PermissionGroupDTO>>(db =>
{
return db.PermissionGroups
.Include(i => i.GroupActionPermissions)
.ProjectTo<PermissionGroupDTO>()
.ToList();
});
}
And I am getting collection of PermissionGroupDTO which should contains collection of GroupActionPermissionDTO, but that collection stays null. Is there something wrong with my code? I am afraid that automapper can map collections from foreign keys.
Also, thats my automapper initializer:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>();
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});
I believe the reason is desribed here http://docs.automapper.org/en/stable/Queryable-Extensions.html
Note that for this feature to work, all type conversions must be explicitly handled in your Mapping.
So that means you should manually configure the mapping:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>()
.ForMember(dto => dto.ActionPermissions , conf => conf.MapFrom(ol => ol.GroupActionPermissions )));;
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});
BTW, note that fields are named differently: GroupActionPermissions vs. ActionPermissions. This is also the reason why automapper doesn't map it automatically and then you should use the manual configuration I wrote.
I'm struggling using AutoMapper to map these objects.
Here are my DTO:
public class ContainerDTO
{
public List<MoneyAccountDTO> MoneyAccounts { get; set; }
public List<CardDTO> Cards { get; set; }
}
public class MoneyAccountDTO
{
public string Iban { get; set; }
}
public class CardDTO
{
public string MoneyAccountIban { get; set; }
}
Here are the BusinessObject:
public class Container
{
public List<MoneyAccount> MoneyAccounts { get; set; }
}
public class MoneyAccount
{
public string Iban { get; set; }
public List<Card> Cards { get; set; }
}
public class Card
{
public string MoneyAccountIban { get; set; }
}
What i try to achieve here, is to find all CardDTO in the ContainerDTO that have the same Iban than a MoneyAccount and Create a list of these cards in the MoneyAccount itself.
The probleme here is that i receive the CardsDTO object at the same level as the MoneyAccountDTO it related to.
I started with something like that:
cfg.CreateMap<ContainerDTO, Container>()
.ForMember(dest => dest.MoneyAccounts, opt => opt.MapFrom(src => src.Cards.
Where(c => c.MoneyAccountIban == XXX)));
But i'm unable to replace those XXX by the Money account the mapper is enumerating. It think i'm not in the right direction.
I'm unable to find a good solution for this mapping.
Any Help will be appreciated.
The solution for this problem is to use a foreach with a where clause.
cfg.CreateMap<ContainerDTO, Container>().AfterMap((src, dest) => dest.MoneyAccounts
.ForEach(a => a.Cards = src.Cards
.Where(c => c.MoneyAccountIban == a.Iban)
.Select(d => Mapper.Map<CardDTO, ICard>(d)).ToList()));
It works like a charm !
I've got a source model defined as
public class SourceRoot
{
...
public Organisation Organisation { get; set; }
...
}
public class Organisation
{
public long? Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
public double SupplementaryAmount { get; set; }
public decimal BaseConversionRate { get; set; }
}
and a destination defined as:
public class DestinationRoot
{
...
public Organisation Organisation { get; set; }
public ContributesTo ContributesTo { get; set; }
}
public class Organisation
{
public long? Id { get; set; }
public string Name { get; set; }
}
public class ContributesTo
{
public string Currency { get; set; }
public double SupplementaryAmount { get; set; }
public decimal BaseConversionRate { get; set; }
}
I want to map from the SourceRoot to the DestinationRoot add copy from the source Organisation to the destination Organisation AND ContributesTo.
I have the following configuration for AutoMapper:
public static class AutoMapperConfig
{
public static MapperConfiguration RegisterMappings()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MyProfile>();
});
return config;
}
}
public class MyProfile : Profile
{
protected override void Configure()
{
this.CreateMap<SourceRoot, DestinationRoot>();
this.CreateMap<Source.Organisation, Destination.Organisation>();
this.CreateMap<Source.Organisation, Destination.ContributesTo>();
}
}
Using this current profile the Organisation gets mapped but the ContributesTo comes out as null.
Note that I'm using version 4.2 of AutoMapper where the static methods have been deprecated so trying to steer away from that. Normally I would do:
Mapper.CreateMap<SourceRoot, DestinationRoot>()
.ForMember(d => d.ContributesTo, opt => opt.MapFrom( s=> Mapper.Map<ContributesTo>(s.Organisation)));
But this is not advised anymore (referencing the static methods). Is there an alternative way of doing this?
Thanks
Just add mapping for ContributesTo destination member:
protected override void Configure ()
{
CreateMap<Source.SourceRoot, Destination.DestinationRoot>()
.ForMember(d => d.ContributesTo, opt => opt.MapFrom(s => s.Organisation));
CreateMap<Source.Organisation, Destination.Organisation>();
CreateMap<Source.Organisation, Destination.ContributesTo>();
}
Otherwise Automapper finds that both source and destinaton roots have property Organisation and it maps only this property. Automapper cannot understand that it should use one property of source to map several properties of destination (which do not match by name). Note that you don't need to specify mapping for Organisation member, because it matches property name in destination object.
I'm new to Automapper and am trying to map the data from an object in a list to an interface in a list. Add to that the lists are in different levels of the containing objects:
// class definitions
public class MyViewModel
{
public int ViewModelId { get; set; }
public IList<ViewItem> Page1Selections { get; set; }
public IList<ViewItem> Page2Selections { get; set; }
}
public class ViewItem
{
public int ItemId { get; set; }
public bool IsSelected { get; set; }
}
public class MyDbModel
{
public int DbModelId { get; set; }
public IPageSelection Page1Selections { get; set; }
public IPageSelection Page2Selections { get; set; }
}
public interface IPageSelection
{
int PageNumber { get; set; }
IList<IMyDbItem> PageSelections { get; set; }
}
public class PageSelection : IPageSelection
{
public int PageNumber { get; set; }
public IList<IMyDbItem> PageSelections { get; set; }
}
public interface IMyDbItem
{
int ItemId { get; set; }
bool IsSelected { get; set; }
}
public class MyDbItem : IMyDbItem
{
public int ItemId { get; set; }
public bool IsSelected { get; set; }
}
// mapping code
MyViewModel myVm = new MyViewModel();
myVm.ViewModelId = 123;
myVm.Page1Selections = new List<ViewItem>();
myVm.Page1Selections.Add(new ViewItem() { ItemId = 1, IsSelected = true });
myVm.Page2Selections = new List<ViewItem>();
myVm.Page2Selections.Add(new ViewItem() { ItemId = 2, IsSelected = true });
Mapper.Initialize(cfg =>
{
cfg.BindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
cfg.CreateMap<MyViewModel, MyDbModel>();
cfg.CreateMap<ViewItem, IMyDbItem>();
});
MyDbModel myDb = new MyDbModel();
myDb = Mapper.Map<MyDbModel>(myVm); //<== exception here
Sorry for the long attachment. But I'm fighting both an object to interface and hierarchical mismatch mapping problem.
Here's one way you could accomplish this:
cfg.CreateMap<MyViewModel, MyDbModel>()
.ForMember(dest => dest.DbModelId, opt => opt.MapFrom(src => src.ViewModelId));
cfg.CreateMap<ViewItem, IMyDbItem>()
.ConstructUsing((ViewItem src) => new MyDbItem());
cfg.CreateMap<IList<ViewItem>, IPageSelection>()
.ConstructUsing((IList<ViewItem> src) => new PageSelection())
.ForMember(dest => dest.PageSelections, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.PageNumber, opt => opt.Ignore());
The error you were getting is because no mapping existed from IList<ViewItem> to IPageSelection. I've created that mapping above.
An important bit is the ConstructUsing calls. I added those here because I assumed you wanted to use the concrete types that implement the interfaces you're mapping to. Otherwise, AutoMapper will create a proxy class that implements the destination interface.
The part that enables mapping to the IPageSelection.PageSelections from IList<ViewItem> is the opt.MapFrom(src => src) part. This essentially tells AutoMapper to map from the list to the inner PageSelections property, ultimately using the mapping from ViewItem to IMyDbItem.
Example: https://dotnetfiddle.net/DW9dfO