Automapper - Map one object to another usingdiffrent properties - automapper

could you help me. I have one object
class Source{
public string A {get;set;}
public string B {get;set;}
public string C {get;set;}
public string D {get;set;}
}
And destination object
class Dest{
public string A1 {get;set;}
public string B1 {get;set;}
}
Is it possible using Automapper to Create Dest object from source object but first instance of Dest with mapping A to A1 and B to B1, in the second instance of Dest mapping C to A1 and D to B1?
In dreaming solution to get Collection?
Something like:
Mapper.Initialize(cfg =>{
cfg.CreateMap<Source, Dest>()
.ForMember(dest => dest.A1, opt => opt.MapFrom(src => src.A))
.ForMember(dest => dest.B1, opt => opt.MapFrom(src => src.B;))
cfg.CreateMap<Source, Dest>()
.ForMember(dest => dest.A1, opt => opt.MapFrom(src => src.C))
.ForMember(dest => dest.B1, opt => opt.MapFrom(src => src.D))});
Or:
Mapper.Initialize(cfg =>{
cfg.CreateMap<Source, ICollection<Dest>>()})
[UPDATE]
Maybe better solution will be to use reflection and write custom mapper, with attribute [Attribute(MapType, Order, propertyName)]. E.g
class Source{
[Dest(Dest,1,"A1")]
public string A {get;set;}
[Dest(Dest,1,"B1")]
public string B {get;set;}
[Dest(Dest,2,"A1")]
public string C {get;set;}
[Dest(Dest,2,"B1")]
public string D {get;set;}
}
New go through properties and create object using type from attributes.

You can create two mappers:
var m1 = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<Source, Dest>()
.ForMember(dest => dest.A1, opt => opt.MapFrom(src => src.A))
.ForMember(dest => dest.B1, opt => opt.MapFrom(src => src.B));
})
.CreateMapper();
var m2 = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<Source, Dest>()
.ForMember(dest => dest.A1, opt => opt.MapFrom(src => src.C))
.ForMember(dest => dest.B1, opt => opt.MapFrom(src => src.D));
})
.CreateMapper();
And then use them behind another interface (that you define) (e.g. ISourceMapper) which would alternate between the two mappers based on the number of the instance (i.e. instance 1 -> mapper 1, instance 2 -> mapper 2, instance 3 -> mapper 1 ...etc.)
For the collection part, you can then easily call IEnumerable.Select passing ISourceMapper.Map method to the projection Func<,>.

Related

Automapper does not map List<T> to List<T> while mapping List

I'm using automapper with CQRS pattern. Below is my class which takes input from .net core API. The API takes collection as input and I'm sending collection in my Mediatr Command object. In Mediatr command, I'm mapping source collection to destination collection and while doing mapping, I'm getting following exception:
AutoMapper.AutoMapperMappingException
HResult=0x80131500
Message=Error mapping types.
Inner Exception 1:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I'm using follwing code for mapping:
var insertData = _mapper.Map<List<Source>, List<Destination>>(request.Data.ToList());
In my class, I have following:
public class Source: ICustomMapping
{
public int? Prop1 { get; set; }
public string Prop2 { get; set; }
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<Destination, Source>()
.ForMember(dto => dto.Prop1 , opt => opt.MapFrom(p => p.Prop1 ))
.ForMember(dto => dto.Prop2, opt => opt.MapFrom(p => p.Prop2))
;
}
}
This mapping works flawlessly when I have single object in both ways (forward and reverse). Now I need to pass collection of object for processing and save destination collection data in to database.
After looking in to the documentation I realize that I do not have the reverse mapping.
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<Destination, Source>()
.ForMember(dto => dto.Prop1 , opt => opt.MapFrom(p => p.Prop1 ))
.ForMember(dto => dto.Prop2, opt => opt.MapFrom(p => p.Prop2))
.ReverseMap();
}
The ReverseMap() I was missing.
Thanks

AutoMapper map source property into inner destination object

I have one source class:
class Source {
public string Name;
public string Field1;
public string Field2;
}
and two destination classes:
class Destination {
public string Name;
public FieldsDto Fields;
}
class FieldsDto {
public string Field1;
public string FieldTwo;
}
How can I map Source.Field1 to Destination.Fields.Field1 and Source.Field2 to Destination.Fields.FieldTwo?
This code does not work; it would throw an error saying that Custom configuration for members is only supported for top-level individ
ual members on a type1:
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Fields.Field1, opt => opt.Mapfrom(src => src.Field1)
.ForMember(dest => dest.Fields.FieldTwo, opt => opt.Mapfrom(src => src.Field2);
});
As mentioned in the comments, in order to map nested properties, you will need to use ForPath instead of ForMember. So a full configuration may look like this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(s => s.Name))
.ForPath(dest => dest.Fields.Field1, opt => opt.MapFrom(src => src.Field1))
.ForPath(dest => dest.Fields.FieldTwo, opt => opt.MapFrom(src => src.Field2));
});
If you want to do this dynamically, using member names as string (which appears like something you want to do, as I learned in chat), then you will not be able to use ForPath easily since that absolutely requires a lambda expression that contains only a member expression.
What you could do is create a lambda expression dynamically for the nested member access. I’m sure you will find enough examples on how to create such lambda expressions on here if you search for it.
The alternative would be to split up the mapping into the separate types. So instead of mapping directly to nested properties of the Destination, you are mapping the nested object separately. Like this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Source, FieldsDto>()
.ForMember("Field1", opt => opt.MapFrom("Field1"))
.ForMember("FieldTwo", opt => opt.MapFrom("Field2"));
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(s => s.Name))
.ForMember(dest => dest.Fields, opt => opt.MapFrom(s => Mapper.Map<FieldsDto>(s)));
});

AutoMapper map object to Lookup field in MS Dynamics CRM

I am trying to map using AutoMapper.
Source object is C# object
public class Source
{
public string comment { get; set; }
public string Subject { get; set; }
public Account Account{ get; set; }
}
public class Account
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
My destination is a CRM entity named crm_destination which has fields like comment , Subject but it also has a LookUp field account of type Account.
But I don't know how to map LookUp field.
Following is my Automapper
AutoMapper.Mapper.CreateMap<Source, Destinaetion>()
.ForMember(dest => dest.comment, opt => opt.MapFrom(src => src.comment))
.ForMember(dest => dest.account, opt => opt.MapFrom(src => src.account));
.ForMember(dest => dest.account, opt => opt.MapFrom(src => src.account)) is throwing error of type mismatch..
basically my problem is I don't know how to map LookUp field where CRM entity is destination.
You need to use a custom resolver in AutoMapper. In your custom resolver you'll need to query CRM to get the ID of the account record in that matches your Account object and return an EntityReference to this object.
Lookups are stored as EntityReferences in CRM. So you'll need to convert your account to an EntityReference.
I have never used AutOMapper, but what Nicknow suggests makes sense. Just call the ToEntityReference() method on Entity.
I solved it. Lookup creates a relationship. my lookp created a relationship name cxrm_account_cxrm_source_account
so instead of .ForMember(dest => dest.account, opt => opt.MapFrom(src => src.account))
I did
.ForMember(dest => dest.cxrm_account_cxrm_source_account, opt => opt.MapFrom(src => src.Account))
now it is working

AutoMapper Map string to OptionSet value in MS Dynamics CRM

I am using AutoMapper.
My source object is simple class
public class Source
{
public string FirstName { get; set; }
public string type{ get; set; }
}
My destination is a MS Dynamics CRM Entity ( I have generated the model using CrmSvctil) which contains an option set named type.
Following is my mapping
AutoMapper.Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.type, opt => opt.MapFrom(src => src.type));
I am getting error is type mismatch
Basically my problem is
I don't know how to map string to an Option set value using AutoMapper
OptionSets are stored as OptionSetValues that have a Value Property of type Int, not Strings, hence your type mismatch error.
If your type is an actual int, you just need to parse it:
AutoMapper.Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.type, opt => opt.MapFrom(src => new OptionSetValue(int.parse(src.type))));
But if it's the actual text value for the option set, you'll need to lookup the text values using the OptionSetMetaData:
public OptionMetadataCollection GetOptionSetMetadata(IOrganizationService service, string entityLogicalName, string attributeName)
{
var attributeRequest = new RetrieveAttributeRequest
{
EntityLogicalName = entityLogicalName,
LogicalName = attributeName,
RetrieveAsIfPublished = true
};
var response = (RetrieveAttributeResponse)service.Execute(attributeRequest);
return ((EnumAttributeMetadata)response.AttributeMetadata).OptionSet.Options;
}
var data = GetOptionSetMetadata(service, "ENTITYNAME", "ATTRIBUTENAME");
AutoMapper.Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.type, opt => opt.MapFrom(src => new OptionSetValue(optionList.First(o => o.Label.UserLocalizedLabel.Label == src.type))));

Automapper expression must resolve to top-level member

I am using automapper to map source and destination objects. While I map them I get the below error.
Expression must resolve to top-level member. Parameter name: lambdaExpression
I am not able resolve the issue.
My source and destination objects are:
public partial class Source
{
private Car[] cars;
public Car[] Cars
{
get { return this.cars; }
set { this.cars = value; }
}
}
public partial class Destination
{
private OutputData output;
public OutputData Output
{
get { return this.output; }
set { this.output= value; }
}
}
public class OutputData
{
private List<Cars> cars;
public Car[] Cars
{
get { return this.cars; }
set { this.cars = value; }
}
}
I have to map Source.Cars with Destination.OutputData.Cars object. Could you please help me in this?
You are using :
Mapper.CreateMap<Source, Destination>()
.ForMember( dest => dest.OutputData.Cars,
input => input.MapFrom(i => i.Cars));
This won't work because you are using 2 level in the dest lambda.
With Automapper, you can only map to 1 level. To fix the problem you need to use a single level :
Mapper.CreateMap<Source, Destination>()
.ForMember( dest => dest.OutputData,
input => input.MapFrom(i => new OutputData{Cars=i.Cars}));
This way, you can set your cars to the destination.
Define mapping between Source and OutputData.
Mapper.CreateMap<Source, OutputData>();
Update your configuration to map Destination.Output with OutputData.
Mapper.CreateMap<Source, Destination>().ForMember( dest => dest.Output, input =>
input.MapFrom(s=>Mapper.Map<Source, OutputData>(s)));
You can do it that way:
// First: create mapping for the subtypes
Mapper.CreateMap<Source, OutputData>();
// Then: create the main mapping
Mapper.CreateMap<Source, Destination>().
// chose the destination-property and map the source itself
ForMember(dest => dest.Output, x => x.MapFrom(src => src));
That's my way to do that ;-)
ForPath works for this exact scenario.
Mapper.CreateMap<Destination, Source>().ForPath(dst => dst.OutputData.Cars, e => e.MapFrom(src => src.Cars));
This worked for me:
Mapper.CreateMap<Destination, Source>()
.ForMember(x => x.Cars, x => x.MapFrom(y => y.OutputData.Cars))
.ReverseMap();
The correct answer given by allrameest on this question should help: AutoMapper - Deep level mapping
This is what you need:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.OutputData, opt => opt.MapFrom(i => i));
Mapper.CreateMap<Source, OutputData>()
.ForMember(dest => dest.Cars, opt => opt.MapFrom(i => i.Cars));
When using the mapper, use:
var destinationObj = Mapper.Map<Source, Destination>(sourceObj)
where destinationObj is an instance of Destination and sourceObj is an instance of Source.
NOTE: You should try to move away from using Mapper.CreateMap at this point, it is obsolete and will be unsupported soon.

Resources