I have a rather complex situation.
I have an order:
public partial class Order
{
public string orderNum { get; set;}
public ICollection<Shipment> Shipments { get; set; }
...
}
I have a view BIGVIEW:
public partial class BIGVIEWVM
{
public string orderNum { get; set;}
public OrderDetailsVM Order { get; set; }
public ShipmentsTableVM Shipments { get; set; }
}
The subview, ShipmentsTableVM is like so:
public partial class ShipmentsTableVM
{
public string somethingelse {get;set;}
public IEnumerable<Shipment_Table_Row_VM> Shipments{get;set;}
}
It's actually more complex than this, but this is the issue in its simplest form.
I have an Order with lots of Shipments. I want to make a ProjectTo call using AutoMapper from the Order to the BIGVIEW. The entire of the Order is mapped into the OrderDetailsVM with no problem, however, What I do not know how to do is pass the ICollection to my ShipmentsTableVM and THEN map it into the IEnumerable.
cfg.CreateMap<Order, BIGVIEW>()
.ForMember(d => d.order, opt => opt.MapFrom(s => s))
...?
I thought I could take care of the mapping from the ICollection<Shipments> to my ShipmentsTableVM:
cfg.CreateMap<IEnumerable<Shipment>, ShipmentsTableVM>()
.ForMember(d => d.Shipments, opt => opt.MapFrom(s => s))
;
But this doesn't work - I get an error about Linq not being able to map things that are not an IEnumerable... but I am using an IEnumerable so I don't understand. I have tried a variety of other configurations too but I'm getting nowhere fast.
I am looking for an Automapper solution for this, as I do not want to manually do the projection in my controller.
Thanks
There are few ways to achieve this, here is one of them.
cfg.CreateMap<Shipments, Shipment_Table_Row_VM>();
cfg.CreateMap<ICollection<Shipments>, ShipmentsTableVM>()
.ConstructUsing(obj => new ShipmentsTableVM())
.ForMember(d => d.Shipments, opt => opt.MapFrom(s => s));
cfg.CreateMap<Order, BIGVIEWVM>();
You need to pass proper model to subview. try :
Order.Shipments
or
Model.Shipments
Related
I have a question about AutoMapper that I am struggling to solve
Consider a scenario where we are doing a mapping between a Business type to an API type:
public class BusinessType
{
public int ID { get; set; }
public string Value { get; set; }
public bool Visible { get; set; }
}
public class ApiType
{
public int ID { get; set; }
public string Value { get; set; }
}
Creating a map for this is straightforward. Assuming that we want to explicitly map each property:
cfg.CreateMap<BusinessType, ApiType>()
.ForMember(dest => dest.ID, opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Value));
However, the complication for me is how to do this where we filter out any records for the API type where the business type has visible = 0.
For example, if five BusinessType values are contained in a list, and three of those values had visible = 1 and two had visible = 0, I would want the resulting list of ApiTypes to only contain three records, which would be those from the original list that had visible = 1.
Does anyone know how I might achieve that?
Many thanks and best regards
I found out how to do it, with some experimentation:
cfg.CreateMap<List<BusinessType>, List<ApiType>>()
.ConvertUsing((src, dest) => dest = ConvertAndFilter(src));
private List<ApiType> ConvertAndFilter(List<BusinessType>)
{
// do code in here to covert the list, filtering out those with visible = 0
}
I have an object model something like this:
public class Concert {
public Band Band { get; set; }
public ConcertVenue Venue { get; set; }
}
public class TicketOrder {
public Concert Concert { get; set; }
public string CustomerName { get; set; }
}
// DTOs for email and web views:
public class ConcertDto {
public string Artist { get; set; }
public string Venue { get; set; }
}
public class TicketOrderDto : ConcertDto {
public string CustomerName { get; set; }
}
I'm using AutoMapper to map domain objects to DTOs. The DTOs here have an inheritance relationship that doesn't exist in the domain model (because when I send an email about a ticket order, I want to include all the information about the concert)
I have a mapping defined like this:
config.CreateMap<Concert, ConcertDto>()
.ForMember(dto => dto.Artist, opt => opt.MapFrom(concert => concert.Band.Name))
.ForMember(dto => dto.Venue, opt => opt.MapFrom(concert => concert.GetVenueSummary());
config.CreateMap<TicketOrder, ConcertDto>()
.ForMember(dto => dto.Artist, opt => opt.MapFrom(concert => concert.Band.Name))
.ForMember(dto => dto.Venue, opt => opt.MapFrom(concert => concert.GetVenueSummary())
.ForMember(dto => dto.CustomerName, optn.MapFrom(order => order.Customer.FullName))
;
There's some duplication in those maps, and what I want to do is to reuse the Concert > ConcertViewData mapping when I map the TicketOrderDto:
cfg.CreateMap<TicketOrder, TicketOrderDto>()
// This is what I *want* but isn't valid AutoMapper syntax:
.IncludeMembers(dto => dto, order => order.Concert)
.ForMember(dto => dto.CustomerName, optn.MapFrom(order => order.Customer.FullName));
but this fails with:
System.ArgumentOutOfRangeException: Only member accesses are allowed.
dto => dto (Parameter 'memberExpressions')
at AutoMapper.ExpressionExtensions.EnsureMemberPath(LambdaExpression exp, String name)
Calling .IncludeBase<> doesn't work, because ConcertOrder doesn't derive from Concert.
Is there an easy way to import one map into another but specify that it should map from a child object of the source type? i.e. "hey, please map source.Child onto this, and then run the regular source > this mapping?"
I am going to make an assumption here, but I believe the mapping should be from TicketOrder to TicketOrderDto, and not ConcertDto (which contains no CustomerName property) as the given models don't match the given mapping configuration.
In that case, you should be able to use .AfterMap() on ticket mapper configuration to map from Concert to ConcertDto.
cfg.CreateMap<TicketOrder, TicketOrderDto>()
.ForMember(d => d.CustomerName, o => o.MapFrom(s => s.Order.Customer.Name))
.AfterMap((s, d, context) => context.Mapper.Map(s.Concert, d));
I have a model which I am trying to map from Match class in .net core 2.0.
Both the classes have a Name property.
I need to map Match.Value => ViewCompany.Name
But it always puts Match.Name into ViewCompany.Name
Here is my AutomapperProfile:
CreateMap<Match, ViewCompany>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Value));
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Value))
ViewCompany:
public class ViewCompany
{
public ViewCompany()
{
}
public ViewCompany(string name)
{
this.Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
}
The above mapping doesn't work.
But if I change property name in the model to something else like "Value" or "tempName" and update the automapper profile, it works fine.
So, is it not possible to map properties with same names to different properties in Automapper?
What happens here is that Name is mapped through the constructor. A simple way to avoid that is to tell AM what constructor to use:
CreateMap<Match, ViewCompany>().ConstructUsing(source=>new ViewCompany());
I'm having some trouble trying to map these two classes (Control -> ControlVM)
public class Control
{
public IEnumerable<FieldType> Fields { get; set; }
public class FieldType
{
//Some properties
}
}
public class ControlVM
{
public FieldList Fields { get; set; }
public class FieldList
{
public IEnumerable<FieldType> Items { get; set; }
}
public class FieldType
{
//Properties I'd like to map from the original
}
}
I tried with opt.ResolveUsing(src => new { Items = src.Fields }) but apparently AutoMapper cannot resolve the anonymous type. Also tried extending ValueResolver, but didn't work either.
NOTE: This VM is later used in a WebApi, and JSON.NET needs a wrapper around the collection to properly deserialize it. So removing the wrapper is not a solution.
NOTE2: I'm also doing Mapper.CreateMap<Control.FieldType, ControlVM.FieldType>(), so the problem isn't there.
This works for me:
Mapper.CreateMap<Control.FieldType, ControlVM.FieldType>();
// Map between IEnumerable<Control.FieldType> and ControlVM.FieldList:
Mapper.CreateMap<IEnumerable<Control.FieldType>, ControlVM.FieldList>()
.ForMember(dest => dest.Items, opt => opt.MapFrom(src => src));
Mapper.CreateMap<Control, ControlVM>();
Update: Here's how to map the other way:
Mapper.CreateMap<ControlVM.FieldType, Control.FieldType>();
Mapper.CreateMap<ControlVM, Control>()
.ForMember(dest => dest.Fields, opt => opt.MapFrom(src => src.Fields.Items));
I have something like this
public class ProductViewModel
{
public int SelectedProductId { get; set; }
public string ProductName {get; set;}
public int Qty {get; set;}
public List<SelectListItem> Products { get; set};
}
I have a domain like this
public class Product
{
public int ProductId {get; set;}
public string ProductName {get; set;}
public int Qty {get; set;}
}
public class Store
{
public Product() {get; set;}
}
Now I need to do the mapping.
// in my controller
var result = Mapper.Map<ProductViewModel, Store>(Product);
this won't bind anything since it can't figure out how to put the ProductId in since it
is
Store.Product.ProductId;
My map is like this
Mapper.CreateMap<ProductViewModel, Store>().ForMember(dest => dest.Product.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
I get this error
Expression 'dest =>
Convert(dest.Product.SelectedProductId' must
resolve to top-level member. Parameter
name: lambdaExpression
I am unsure how to do this.
To Map nested structures, you just need to create a new object in the MapFrom argument.
Example
Mapping:
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.MyNestedType, o => o.MapFrom(t => new NestedType { Id = t.Id }));
Mapper.AssertConfigurationIsValid();
Test Code:
var source = new Source { Id = 5 };
var destination = Mapper.Map<Source, Destination>(source);
Classes:
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public NestedType MyNestedType { get; set; }
}
public class NestedType
{
public int Id { get; set; }
}
You can use Resolver.
Create a resolver class like that :
class StoreResolver : ValueResolver<Store, int>
{
protected override int ResolveCore(Store store)
{
return store.Product.ProductId;
}
}
And use it like that :
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.SelectedProductId, opt => opt.ResolveUsing<StoreResolver >());
Hope it will help ...
The error your getting is because you cannot declare mapping declarations more than one level deep in your object graph.
Because you've only posted one property its hard for me to give you the codes that will make this work. One option is to change your viewmodel property to MyTestTestId and the conventions will automatically pick up on that.
The correct answer given by allrameest on this question should help: AutoMapper - Deep level mapping
This is what you need:
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.Product, opt => opt.MapFrom(src => src));
Mapper.CreateMap<ProductviewModel, Product>()
.ForMember(dest => dest.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
NOTE: You should try to move away from using Mapper.CreateMap at this point, it is obsolete and will be unsupported soon.