I've browsed documentation on mapping collections and nested mapping and mapping of nested collection, but still can't cope with my case.
I have the following json config file:
{
"startupConfig": {
"noSubscription": {
"calls": [
{
"percentage": 30,
"techPriority": 1,
"timePriority": 2
},
{
"percentage": 30,
"techPriority": 1,
"timePriority": 2
}
]
}
}
}
And here is my code reading from the file:
var config = _mapper.Map<FeedConfiguration>(_configuration
.GetSection("startupConfig").GetChildren()
.FirstOrDefault(cc => cc.Key == "noSubscription")?.GetChildren());
However, the mapping does not work. Here is the code of mapping configuration:
CreateMap<IEnumerable<IConfigurationSection>, CallConfiguration>()
.ForMember(cc => cc.Percentage,
mo => mo.MapFrom(css => css.FirstOrDefault(cs => cs.Key == "percentage").Value))
.ForMember(cc => cc.TechPriority,
mo => mo.MapFrom(css => css.FirstOrDefault(cs => cs.Key == "techPriority").Value))
.ForMember(cc => cc.TimePriority,
mo => mo.MapFrom(css => css.FirstOrDefault(cs => cs.Key == "timePriority").Value));
CreateMap<IEnumerable<IConfigurationSection>, FeedConfiguration>()
.ForMember(fc => fc.CallsConfig,
mo => mo.MapFrom(css => css.FirstOrDefault(cs => cs.Key == "calls").GetChildren()));
Classes that I am mapping to:
namespace FeedService.FeedConfigurations
{
public class FeedConfiguration
{
public ICollection<CallConfiguration> CallsConfig { get; set; }
}
public class CallConfiguration
{
public int Percentage { get; set; }
public int TechPriority { get; set; }
public int TimePriority { get; set; }
}
}
And here is an exception I get:
AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
=============================================================================================================
AutoMapper created this type map for you, but your types cannot be mapped using the current configuration.
IConfigurationSection -> CallConfiguration (Destination member list)
Microsoft.Extensions.Configuration.IConfigurationSection -> FeedService.FeedConfigurations.CallConfiguration (Destination member list)
Unmapped properties:
Percentage
TechPriority
TimePriority
Would be very thankful for your help!
===Note===
I still need an answer for the question but I posted the new one with better explanation here.
You don't actually need Automepper here. Just use default .net core binding.
rename this one
public ICollection<CallConfiguration> CallsConfig { get; set; }
to Calls
then use something like
var config = _configuration.GetSection("startupConfig:noSubscription").Get<FeedConfiguration>();
Related
I am having problems mapping my domain object to my DTO object.
The error is:
Expression must resolve to top-level member and not any child object's properties. You can use ForPath, a custom resolver on the child type or the AfterMap option instead.
public class Sign
{
public List<Item> Items { get; set; } = new List<Item>();
}
public class SignDTO
{
public SignItemDTO Items { get; set; } = new SignItemDTO();
}
public class SignItemDTO
{
public List<ItemDTO> Items { get; set; } = new List<ItemDTO>();
}
private MapperConfiguration AutoMapperConfig()
{
return new MapperConfiguration(cfg =>
{
cfg.CreateMap<Item, ItemDTO>();
cfg.CreateMap<Sign, SignDTO>().ForPath(dest => dest.Items.Items, opt => opt.MapFrom(src => src.Items));
});
}
_context.Sign.Include(m => m.Items)
.ProjectTo<SignDTO>(AutoMapperConfig());
Probably it's due to your map in this line:
cfg.CreateMap<Sign, SignDTO>().ForPath(dest => dest.**Items.Items**, opt => opt.MapFrom(src => src.**Items**));
As you can see in between the **, you're maping from an entity property to another entity children's property, this is what AutoMapper does not like and what you see in the error message.
Maybe it was a mistake on your side an you can removed one of the Items or you can create a new mapping for your first level of Items which takes care of the childrens.
How to map following ItemId (as Source) with DataId (as Output):
Source:
public class Source
{
public InputData InputItem { get; set; }
}
public class InputData
{
public int ItemID { get; set; }
}
Output:
public class Output
{
public List<OutputData> OutputItem { get; set; }
}
public class OutputData
{
public string[] DataID { get; set; }
}
I'm trying to map it in following way:
CreateMap<Source, Output>().ForMember(d => d.OutputItem[0].DataID,
option => option.MapFrom(s => s.InputItem != null ? new string[] { $"item_{s.InputItem.ItemID}" } : null));
Getting the exception:
Expression 'd => d.OutputItem.get_Item(0).DataID' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead. Parameter name: lambdaExpression
Can someone please help me in mapping these objects.
Thanks
The error basically says that you can only define a mapping for one level of the object. You can not define the mapping to directly write to a property of a child object.
Solve this by defining separate mappings for the objects on each level.
First define the mapping for the child objects:
CreateMap<InputData, OutputData>().ForMember(d => d.DataID,
option => option.MapFrom(s => s != null ? new string[] { $"item_{s.ItemID}" } : null));
Then the mapping for the parent object can use this definition to map its child objects. (Collections like List<OutputData> should be filled by AutoMapper automatically.)
CreateMap<Source, Output>().ForMember(d => d.OutputItem,
option => option.MapFrom(s => s.InputItem));
In my project I'm trying to use automapper to unflatten my command objects to my domain objects by convention as much as possible.
It works when I explicitly map the two members in the mapping profile, but according to the automapper documentation I think this should also work by convention.
I created a dotnetfiddle to demonstrate the minimal case.
Related questions end up with people explicitly adding the mapping, but that kind of goes against what Automapper is built for and contradicts the documentation, no?
It doesn't work with flattening either, so the reversemap is a red herring I think.
The mapping
public class Mapping: Profile
{
public Mapping()
{
this.CreateMap<CreateSelectionCommand, Selection>();
// .ForMember(selection => selection.Name, opt => opt.MapFrom(x => x.SelectionName))
.reverseMap()
}
}
What I expect to work
[Fact]
public void ShouldMapName()
{
var cmd = new CreateSelectionCommand {SelectionName = "selectionName"};
var selection = _mapper.Map<Selection>(cmd);
Assert.Equal(cmd.SelectionName, selection.Name); <== selection.Name == ""
}
Classes for context
public class Selection
{
public string Name { get; set; }
}
public class CreateSelectionCommand
{
public string SelectionName { get; set; }
}
Did I misread the docs or am I missing something?
Flattening is about mapping nested "complex" objects to properties on "higher" level i.e. in your case if CreateSelectionCommand had property Selection of type which had Name property it would be mapped to SelectionName in destination type (see this fiddle).
You can try to use prefixes by adding:
cfg.RecognizePrefixes("Selection");
to your configuration (see this fiddle) but I doubt that it is suitable option for convention based handling.
Also it seems that you can add custom name convention using ISourceToDestinationNameMapper and AddMemberConfiguration:
class TypeNamePrefixedSourceToDestinationNameMapper : ISourceToDestinationNameMapper
{
public MemberInfo GetMatchingMemberInfo(IGetTypeInfoMembers getTypeInfoMembers, TypeDetails typeInfo,
Type destType,
Type destMemberType, string nameToSearch)
{
return getTypeInfoMembers.GetMemberInfos(typeInfo)
.FirstOrDefault(mi => mi.Name == destType.Name + nameToSearch);
}
}
var config = new MapperConfiguration(cfg =>
{
cfg.AddMemberConfiguration().AddName<TypeNamePrefixedSourceToDestinationNameMapper>();
// ...
}
At least it works in this simple case, see this fiddle.
I have a model which has a generic properties property looking something like this:
public class GenericProperty
{
public string Name { get; set; }
public object Value { get; set; }
}
Next to that I have a object that has a list with GenericProperties like this:
public class GenericEntity
{
public string Name { get; set; }
public List<GenericProperty> Properties { get; set; }
}
Now im calling a API that deserialize the json to the model above. Next i want to use AutoMapper to construct an actual good looking model so i did the following:
Mapper.Initialize(x =>
{
x.CreateMap<GenericEntity, MyModel>()
.ForMember(d => d.ManagerId, o => o.MapFrom(s => s.Properties.Where(n => n.Name == "ManagerId" ).Select(v => v.Value)))
});
Problem is that this returns the property type and not the actual value:
System.Linq.Enumerable+WhereListIterator`1[MyProject.Models.GenericProperty]
How can i lookup the value of within the model?
By using Where you are selecting all the properties with the name ManagerId. You should instead use Single like in this unit test:
public class TestClass
{
[Test]
public void TestMapper()
{
Mapper.Initialize(x =>
{
x.CreateMap<GenericEntity, MyModel>()
.ForMember(d => d.ManagerId,
o => o.MapFrom(s => s.Properties.Single(n => n.Name == "ManagerId").Value));
});
var ge = new GenericEntity
{
Properties = new List<GenericProperty>
{
new GenericProperty {Name = "ManagerId", Value = "Great"}
}
};
var myModel = Mapper.Map<MyModel>(ge);
Assert.AreEqual("Great", myModel.ManagerId);
}
}
If you can not guarantee that there will be a property with the name ManagerId, you should use SingleOrDefault, and handle the null case.
IMHO, you should limit this kind of AutoMapper configurations to simple cases, since it quickly gets difficult to debug and maintain.
For more complex mappings like this one, I would recommend you to make an explicit mapping method instead, which you can call like an extension method. Another option worth considering if you want to use Automapper are Custom value resolvers.
I have a services that is calling another services. Both of the services are using "the same classes". The classes are named same and have the same properties but has different namespace so I need to use AutoMapper to map from one of the type to the other type.
No it's pretty simple since all I have to do is the CreateMap<>, but the problem is that we have around hundreds of classes that I manually needs to write the CreateMap<> from, and it's works wired to me. Isn't there any Auto CreateMap function. So if I say CreateMap() then AutoMapper workes thru Organisation and finds all classes and automatically does the CreateMap for these Classes and it's subclasses etc etc…
Hope for a simple solution, or I guess some reflection can fix it...
Just set CreateMissingTypeMaps to true in the options:
var dto = Mapper.Map<FooDTO>
(foo, opts => opts.CreateMissingTypeMaps = true);
If you need to use it often, store the lambda in a delegate field:
static readonly Action<IMappingOperationOptions> _mapperOptions =
opts => opts.CreateMissingTypeMaps = true;
...
var dto = Mapper.Map<FooDTO>(foo, _mapperOptions);
UPDATE:
The approach described above no longer works in recent versions of AutoMapper.
Instead, you should create a mapper configuration with CreateMissingTypeMaps set to true and create a mapper instance from this configuration:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true;
// other configurations
});
var mapper = config.CreateMapper();
If you want to keep using the old static API (no longer recommended), you can also do this:
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
// other configurations
});
UPDATE 2 - Automapper 9 and later:
Starting from Automapper version 9.0, the CreateMissingTypeMaps API was removed. Automapper documentation now suggests to explicitly configure maps, manually or using reflection.
https://docs.automapper.org/en/stable/9.0-Upgrade-Guide.html#automapper-no-longer-creates-maps-automatically-createmissingtypemaps-and-conventions
CreateMissingTypeMaps can be set within your profile. It's however recommended to explicitly use CreateMap for each mapping and call AssertConfigurationIsValid in your unit tests for each profile to prevent silent errors.
public class MyProfile : Profile {
CreateMissingTypeMaps = true;
// Mappings...
}
AutoMapper has a DynamicMap method which you might be able to use: here's an example unit test illustrating it.
[TestClass]
public class AutoMapper_Example
{
[TestMethod]
public void AutoMapper_DynamicMap()
{
Source source = new Source {Id = 1, Name = "Mr FooBar"};
Target target = Mapper.DynamicMap<Target>(source);
Assert.AreEqual(1, target.Id);
Assert.AreEqual("Mr FooBar", target.Name);
}
private class Target
{
public int Id { get; set; }
public string Name { get; set; }
}
private class Source
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Set CreateMissingTypeMaps option to true. This is package AutoMapper.Extensions.Microsoft.DependencyInjection's example for ASP.NET Core:
public class Startup {
//...
public void ConfigureServices(IServiceCollection services) {
//...
services.AddAutoMapper(cfg => { cfg.CreateMissingTypeMaps = true; });
//...
}
//...
}
In case someone is still interested in this topic, I've created a NuGet package that gives the automatic mapping functionality since AutoMapper removed it in a certain version.
It's available under wakiter.AutoMapper.Extensions name.
To use it, invoke the CreateAutoMap extension method and it'll do the work for you.
Today I needed this in some generic code as well. I tried something like this:
private static IMapper CreateMapper<T1, T2>()
{
return new MapperConfiguration(cfg => FillMapperConfig(cfg, typeof(T1), typeof(T2)))
.CreateMapper();
}
private static void FillMapperConfig(IMapperConfigurationExpression cfg, Type T1, Type T2)
{
if (T1 == T2)
{
return;
}
cfg.CreateMap(T1, T2);
foreach (PropertyInfo propertyInfo in T1.GetProperties())
{
PropertyInfo correspondingProperty =
T2.GetProperties()
.FirstOrDefault(p =>
p.Name == propertyInfo.Name);
if (correspondingProperty != null)
{
if (propertyInfo.PropertyType.IsGenericType &&
correspondingProperty.PropertyType.IsGenericType)
{
FillMapperConfig(
cfg,
propertyInfo.PropertyType.GetGenericArguments()[0],
correspondingProperty.PropertyType.GetGenericArguments()[0]);
}
else if (propertyInfo.PropertyType.IsClass &&
correspondingProperty.PropertyType.IsClass)
{
FillMapperConfig(
cfg,
propertyInfo.PropertyType,
correspondingProperty.PropertyType);
}
}
}
}
Then I can do something like this:
IMapper mapper = CreateMapper<ClassA, ClassB>();
Which creates a map from ClassA to ClassB with all sub properties of ClassA and ClassB if they have the same name and recursively for sub sub properties.
Example:
public class ClassA {
public int IntProperty { get; set; }
public ClassASubProperty SubProperty { get; set; }
public List<ClassAListItem> ListItems { get; set; }
}
public class ClassB {
public int IntProperty { get; set; }
public ClassBSubProperty SubProperty { get; set; }
public List<ClassBListItem> ListItems { get; set; }
}
This should result in the IMapper equivalent:
new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA, ClassB>();
cfg.CreateMap<ClassASubProperty, ClassBSubProperty>();
cfg.CreateMap<ClassAListItem, ClassBListItem>()
}).CreateMapper();