As a new fan of AutoMapper, how would I use it to do the following:
Given the following classes, I want to create FlattenedGroup from Group where the list of item string maps to the title property of Item.
public class Group
{
public string Category { get; set; }
public IEnumerable<Item> Items { get; set; }
}
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
}
public class FlattenedGroup
{
public string Category { get; set; }
public IEnumerable<string> Items { get; set; }
}
Thanks
Joseph
The other thing you can do is create a converter from Item -> string:
Mapper.CreateMap<Item, string>().ConvertUsing(item => item.Title);
Now you don't need to do anything special in your Group -> FlattenedGroup map:
Mapper.CreateMap<Group, FlattenedGroup>();
That's all you'd need there.
Give this a try, you can probably use Linq and a lambda expression to map the list of strings in FlattenedGroup with the titles in Group.
Mapper.CreateMap<Group, FlattenedGroup>()
.ForMember(f => f.Category, opt => opt.MapFrom(g => g.Category))
.ForMember(f => f.Items, opt => opt.MapFrom(g => g.Items.Select(d => d.Title).ToList()));
Make sure you add System.Linq to your using statements
Related
I've got the majority of my automapper maps working but I'm facing a problem trying to translate an enum from the value to the string when part of list of child objects. I have the enum to string converter working when at the top level but it seems when I am converting from RecipeStep to RecipeStepResource it isn't using the map defined for Ingredient to IngredientResource and therefore the conversion from enum to string isn't being called.
I've looked around but can't seem to find a similar example to work from and am having trouble deciphering the automapper help on this which says it should automatically pick up the map defined, which it doesn't seem to be, unsure if this is because the Ingredient items are part of a list. Major code snippets below, any help appreciated.
Model:
public class RecipeStep
{
[Required]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Description { get; set; }
public IList<Ingredient> Ingredients { get; set; }
public Timer Timer { get; set; }
public int RecipeID { get; set; }
[JsonIgnore]
[IgnoreDataMember]
[ForeignKey("RecipeID")]
public Recipe Recipe { get; set; }
}
public class Ingredient
{
[Required]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
public ETypeOfIngredient Type { get; set; }
[Required]
public double Amount { get; set; }
[Required]
public EUnitOfMeasure Unit { get; set; }
public int RecipeStepID { get; set; }
[JsonIgnore]
[IgnoreDataMember]
[ForeignKey("RecipeStepID")]
public RecipeStep RecipeStep { get; set; }
}
Resources:
public class RecipeStepResource
{
public int ID { get; set; }
public string Description { get; set; }
public List<IngredientResource> Ingredients { get; set; }
public TimerResource Timer { get; set; }
}
public class IngredientResource
{
public int ID { get; set; }
public string Type { get; set; }
public string Name { get; set; }
public double Amount { get; set; }
public string Unit { get; set; }
public int RecipeStepID { get; set; }
}
Mapping code:
CreateMap<Ingredient, IngredientResource>()
.ForMember(src => src.Type,
opt => opt.MapFrom(src => src.Type.ToDescriptionString()))
.ForMember(src => src.Unit,
opt => opt.MapFrom(src => src.Unit.ToDescriptionString()));
CreateMap<Timer, TimerResource>();
CreateMap<RecipeStep, RecipeStepResource>()
.ForMember(dest => dest.Ingredients,
opt => opt.MapFrom(src => src.Ingredients))
.ForMember(src => src.Timer,
opt => opt.MapFrom(src => src.Timer));
Enum to string conversion code:
public static string ToDescriptionString<TEnum>(this TEnum #enum)
{
FieldInfo info = #enum.GetType().GetField(#enum.ToString());
var attributes = (DescriptionAttribute[])info.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes?[0].Description ?? #enum.ToString();
}
What I have tried:
Creating an Ingredient -> IngredientResource map
Creating a List<Ingredient> -> List<IngredientResource> map
Adding an AfterMap call to the List<Ingredient> -> List<IngredientResource> map to convert the enum value
None of these have worked. Really struggling to understand why AutoMapper is not picking up the Ingredient to IngredientResource map for a List property on the RecipeStep object, I thought it would have done this automatically.
The issue came down to the parent object, I had it incorrectly mapped with both the model and resource files referring to RecipeStep, instead of RecipeStep -> RecipeStepResource. Really want to thank #Lucian for helping me and making me go back to basics to work through the understanding from a simpler standpoint and building up to a representative model.
I effectively have the following entities defined:
public class Order
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Part
{
public int Id { get; set; }
public string Description { get; set; }
public int? OrderId { get; set; }
public Order Order { get; set; }
}
They are configured so that the OrderId in Part is linked to the Order Id:
// Order Model Builder
var orderModelBuilder = modelBuilder.Entity<Order>()
.ToTable("Orders");
orderModelBuilder.HasKey(i => i.Id);
orderModelBuilder.HasMany<Part>()
.WithOne()
.HasForeignKey(i => i.OrderId);
// Part Model Builder
var partModelBuilder = modelBuilder.Entity<Part>()
.ToTable("Parts");
partModelBuilder.HasKey(i => i.Id);
partModelBuilder.HasOne(i => i.Order)
.WithMany()
.HasForeignKey(i => i.OrderId);
Now I would like to map these to a detailed order DTO which includes a collection of parts for an order:
public class PartDto
{
public int Id { get; set; }
public string Description { get; set; }
}
public class OrderDetailsDto
{
public int Id { get; set; }
public string Name { get; set; }
public List<PartDto> Parts { get; set; }
}
Currently, I use Automapper's ProjectTo() to handle queries so I can query what I want and get a list of orders back already mapped to my desired DTO. Now, I want to add Parts to that query so I can get all of the orders back with parts in one query without having to loop through orders and individually fetch the parts for each order returned. I could easily do that if List was part of the Order entity class but adding it at this stage isn't really an option.
CreateMap<Part, PartDto>()
.ForMember(d => d.Id, a => a.MapFrom(s => s.Id))
.ForMember(d => d.Description, a => a.MapFrom(s => s.Description))
;
CreateMap<Order, OrderDetailsDto>()
.ForMember(d => d.Id, a => a.MapFrom(s => s.Id))
.ForMember(d => d.Name, a => a.MapFrom(s => s.Name))
.ForMember(d => d.Parts, a => a.MapFrom(s => ?????))
;
.
..
...
await dbContext.Set<Order>().Where(...).ProjectTo<OrderDetailsDto>(configurationProvider).ToListAsync()
I know I can get shadow variables using EF.Property(object, name) but not sure how to load a restricted collection (dbContext.Set.Where(i => i.OrderId == orderId)). Any suggestions would be greatly appreciated!
--
I'm also open to adding another Entity class if there is a way to make it exist next to the existing entity. So far, I can't find a way to do that without EF Core complaining that it can only have one entity per class (without a discriminator column which would defeat the purpose!).
Trying to map the following
public class WorkPreferance
{
public int ID { get; set; }
public string Name { get; set; }
public IEnumerable<Location> PreferedLocations { get; set; }
}
public class Location
{
public int LocationID { get; set; }
public string LocationName { get; set; }
}
to the following destination ...
public class WorkingPreferenceViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public int LocationID { get; set; }
public string LocationName { get; set; }
}
but I cant wrap my head around what should be the mapping deceleration ..
so far i have this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Location, WorkingPreferenceViewModel>()
.ForMember(d => d.LocationID, o => o.MapFrom(s => s.LocationID))
.ForMember(d => d.LocationName, o => o.MapFrom(s => s.Description))
.ForAllOtherMembers(o => o.Ignore());
cfg.CreateMap<WorkPreferance, WorkingPreferenceViewModel>()
.ForMember(d => d.LocationID, o => o.MapFrom(s => s.PreferedLocations))
.ForMember(d => d.LocationName, o => o.MapFrom(s => s.PreferedLocations))
}
I'm afraid AutoMapper cannot do this. It can map single objects to single objects, and a collection of objects to a collection of objects.
What you want to do is map a single object to a collection of another object. I can't think of how you would use AutoMapper to achieve that.
But, I must say, I don't really understand why you have defined your viewmodel that way. Why would you repeat the Id and Name so many times? In what way does your proposed structure become more easy to process?
What I would do is create a LocationViewModel, and have WorkingPreferenceViewModel contain an IEnumerable<LocationViewModel>. That way you can map it easily with AutoMapper by creating maps between the objects and their respective view models. It will neatly take care of the fact that there is a collection in there.
I have this Party class which contains an object data type coming from a service. It can contain two different member types for the Item property.
public class Party
{
public string DMVID {get; set;}
public object Item { get; set; }
}
and this DTO
public class PartyDTO
{
public string DMVID {get; set;}
public BusinessDTO BusinessItem { get; set; }
public IndividualDTO IndividualItem { get; set; }
}
How can I map the output of the Item to BusinessItem or IndividualItem.
I know this one would not work. Mapper.CreateMap<Party, PartyDTO>();
I don't know if conditional mapping can solve this or a resolver like this one.
Hey maybe this will help you out! I tested it, but i am using AutoMapper just for two days!
Allright here are your noted classes!!!
public class Party
{
public string DMVID { get; set; }
public object Item { get; set; }
}
public class PartyDTO
{
public string DMVID { get; set; }
public BuisnessDTO BusinessItem { get; set; }
public IndividualDTO IndividualItem { get; set; }
}
public class BuisnessDTO
{
public int Number
{
get;
set;
}
}
public class IndividualDTO
{
public string Message
{
get;
set;
}
}
and here your is the MapperConfiguration for this current scenario!
// Edit There was no need here for some conditions
AutoMapper.Mapper.CreateMap<Party, PartyDTO>()
.ForMember(dto => dto.BusinessItem, map =>
map.MapFrom(party => party.Item as BuisnessDTO);
)
.ForMember(dto => dto.IndividualItem, map =>
map.MapFrom(party => party.Item as IndividualDTO);
);
// And this is another way to achive the mapping in this scenario
AutoMapper.Mapper.CreateMap<PartyDTO, Party>()
.ForMember(party => party.Item, map => map.MapFrom( dto => (dto.BusinessItem != null) ? (dto.BusinessItem as object) : (dto.IndividualItem as object)));
And i created this sample for it!
Party firstParty = new Party()
{
DMVID = "something",
Item = new BuisnessDTO()
{
Number = 1
}
};
Party secondParty = new Party()
{
DMVID = "something",
Item = new IndividualDTO()
{
Message = "message"
}
};
PartyDTO dtoWithBuisness = AutoMapper.Mapper.Map<PartyDTO>(firstParty);
PartyDTO dtoWithIndividual = AutoMapper.Mapper.Map < PartyDTO>(secondParty);
Party afterParty = AutoMapper.Mapper.Map<Party>(dtoWithBuisness);
afterParty = AutoMapper.Mapper.Map < Party>(dtoWithIndividual);
Of course there are other possibility, but I think thats exactly what you wanted.
I have a Student object:
public class Student
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
And a Classroom object:
public class Classroom
{
public List<Student> Students { get; set; }
}
I want to use AutoMapper to convert the list of students to a list of student IDs:
public class ClassroomDTO
{
public List<int> StudentIds { get; set; }
}
How do I configure AutoMapper to do this conversion?
Answer:
To expand on my question and Jimmy's answer, this is what I ended up doing:
Mapper.CreateMap<Student, int>().ConvertUsing(x => x.Id);
Mapper.CreateMap<Classroom, ClassroomDTO>()
.ForMember(x => x.StudentIds, y => y.MapFrom(z => z.Students));
AutoMapper was smart enough to do the rest.
You'll need a custom type converter:
Mapper.CreateMap<Student, int>().ConvertUsing(src => src.Id);