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.
Related
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
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)));
});
For some reason AutoMapper gives me a list of the type I need, where all properties are 0 or null. When I debug on data I see my list with all object and properties containing data. But .Map() gives me a list with no data in the properties (but the correct amount of objects). I'm new to AutoMapper, but this does seem very weird. Any suggestions?
public static IMapper Initialize()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<PlantSelectResult, IPlant>()
.ForMember(
dest => dest.description,
opt => opt.MapFrom(src => src.description));
});
return new Mapper(config);
}
And my DataProvider:
public IEnumerable<IPlant> GetPlants()
{
using (var dbCtx = new DataClasses1DataContext(_connectionString.String))
{
var data = dbCtx.PlantSelect().ToList();
return automapper.Map<List<PlantSelectResult>, IPlant[]>(data);
}
}
I didn't realize I had removed set; on the properties. Fixing the interfaces so they were settable fixed the problem.
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))));
In automapper, how would I map a namevalue collection to a strongly typed collection?
Mapper.Map<NameValueCollection, List<MetaModel>>();
public class MetaModel
{
public string Name;
public string Value;
}
Piggybacking off of #dtryon's answer, the tough part about this is that there's no way to map the internal objects in NameValueCollection to your DTO type.
One thing you could do is write a custom converter that constructs KeyValuePair<string, string> objects from the items in the NameValueCollection. This would allow you to create a generic converter that leverages another mapping from KeyValuePair to a destination type of your choosing. Something like:
public class NameValueCollectionConverter<T> : ITypeConverter<NameValueCollection, List<T>>
{
public List<T> Convert(ResolutionContext ctx)
{
NameValueCollection source = ctx.SourceValue as NameValueCollection;
return source.Cast<string>()
.Select (v => MapKeyValuePair(new KeyValuePair<string, string>(v, source[v])))
.ToList();
}
private T MapKeyValuePair(KeyValuePair<string, string> source)
{
return Mapper.Map<KeyValuePair<string, string>, T>(source);
}
}
Then you would need to define a mapping from KeyValuePair<string, string> to MetaModel:
Mapper.CreateMap<KeyValuePair<string, string>, MetaModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Key))
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Value));
And finally, create a mapping between NameValueCollection and List<MetaModel>, using the custom converter:
Mapper.CreateMap<NameValueCollection, List<MetaModel>>()
.ConvertUsing<NameValueCollectionConverter<MetaModel>>();
Well, since NameValueCollection is so special, I don't think there is a good way to do this. This is mostly due to the fact that you can't get a handle on a key/value object inside the NameValueCollection. Luckily the code to map to the List<MetaModel> is not that bad. I would just map it manually and continue working:
[TestMethod]
public void TestMethod2()
{
List<MetaModel> dest = new List<MetaModel>();
NameValueCollection src = new NameValueCollection();
src.Add("Key1", "Value1");
src.Add("Key2", "Value2");
src.Add("Key3", "Value3");
src.Add("Key4", "Value4");
src.Add("Key5", "Value5");
foreach (var srcItem in src.AllKeys)
{
dest.Add(new MetaModel() { Name = srcItem, Value = src[srcItem] });
}
Assert.AreEqual(5, dest.Count);
}