How to map a collection inside a object with automapper - c#

I don't know how to map 'Poses' which is inside my dto coming into my controller.
My DTO looks like this
public class NewInstructorProgramDto
{
public ICollection<int> Poses { get; set; }
}
I'm have an object that looks like this below where I need to map my DTO to this
public class InstructorProgram : BaseEntity
{
public ICollection<InstructorProgramPose> Poses { get; set; }
}
where InstructorProgramPose looks like this
public class InstructorProgramPose : BaseEntity
{
public int PoseId { get; set; }
public Pose Pose { get; set; }
public int InstructorProgramId { get; set; }
public InstructorProgram InstructorProgram { get; set; }
}
In my controller I map it like this
var newInstructorProgram = _mapper.Map<NewInstructorProgramDto, InstructorProgram>(newInstructorProgramDto);
and in my mapping file I need to figure out how to map all the integers for Poses to a collection of 'InstructorProgramPose' objects, where the integer is the PoseId
Here is what I have so far but this won't work because it only maps a single InstructorProgramPose, not a collection
CreateMap<NewInstructorProgramDto, InstructorProgram>()
.ForMember(d => d.Poses, o => o.MapFrom(s => new InstructorProgramPose() { PoseId = s.}));
I think I'm on the right track with this code below
CreateMap<NewInstructorProgramDto, InstructorProgram>()
.ForMember(d => d.Poses, o => o.MapFrom(s => s.Poses));
CreateMap<ICollection<int>, ICollection<InstructorProgramPose>>()
.ForMember(d => d.Select(i => i.PoseId), o => o.MapFrom(s => s.Select(i => i)));
but the debugger is throwing an error that says
Exception has occurred: CLR/AutoMapper.AutoMapperConfigurationException
An exception of type 'AutoMapper.AutoMapperConfigurationException' occurred in AutoMapper.dll but was not handled in user code: 'Custom configuration for members is only supported for top-level individual members on a type.'
UPDATE - Just to give you an idea of what I'm trying to do in Automapper, I've included the code I used to create and fill the object without Automapper, just to show what's needed.
In my controller
var newInstructorProgram = new InstructorProgram() {
Name = newInstructorProgramDto.Name,
Description = newInstructorProgramDto.Description,
Length = (EventLength)newInstructorProgramDto.Length,
Experience = (Experience)newInstructorProgramDto.Experience,
Style = (YogaStyle)newInstructorProgramDto.Style,
Calories = newInstructorProgramDto.Calories,
InstructorId = userFromRepo.Id
};
foreach(var poseId in newInstructorProgramDto.Poses) {
var newPose = new InstructorProgramPose() { PoseId = poseId };
newInstructorProgram.Poses.Add(newPose);
}
_unitOfWork.Repository<InstructorProgram>().Add(newInstructorProgram);

I implemented a solution last night so I'm posting it here.
I added an extension method to the mapping shown below
CreateMap<InstructorProgramForUpdateDto, InstructorProgram>()
.ForMember(d => d.Poses, opt => opt.MapFrom(s => s.Poses.GetInstructorProgramPoses<InstructorProgramPose>()));
Here is the method in the extension class
public static ICollection<T> GetInstructorProgramPoses<T>(this ICollection<int> poses) where T : IInstructorProgramPose, new() {
var instructorProgramPoseColllection = new List<T>();
foreach(var poseId in poses) {
T pose = new T();
pose.PoseId = poseId;
instructorProgramPoseColllection.Add(pose);
}
return instructorProgramPoseColllection;
}

Related

Map two classes in runtime using column mapping information provided in a JSON file

I have two Classes like this.
public class InputModel
{
public int studentid { get; set; }
public string studentname { get; set; }
public string studentcity { get; set; }
}
public class OutputModel
{
public int StudentIDColumn { get; set; }
public string StudentNameColumn { get; set; }
public string StudentCityColumn { get; set; }
}
Now the requirement is like this:
I will receive an object of InputModel Class. From this, I need to create an object of OutputModel class.
It's simple if we use a library like AutoMapper. But the problem is, the Column-To-Column Mapping information will be supplied via a Json File like this:
{
"studentid": "StudentIDColumn",
"studentname": "StudentNameColumn",
"studentcity": "StudentCityColumn"
}
Based on the JSON mapping data, I need to Map the columns in runtime and generate the Output class object.
I tried to map the two classes using Automapper. But I am not sure how to do it in runtime using the JSON file.
var MapperConfig = new MapperConfiguration(c =>
c.CreateMap<InputCSVModel, OutputIDMModel>()
.ForMember(dest => dest.StudentIDColumn, act => act.MapFrom(src => src.studentid))
.ForMember(dest => dest.StudentNameColumn, act => act.MapFrom(src => src.studentname))
.ForMember(dest => dest.StudentCityColumn, act => act.MapFrom(src => src.studentcity))
);
var mapper = new Mapper(MapperConfig);
OutputIDMModel outModel = mapper.Map<OutputIDMModel>(inputModel);
I know that it's might be possible to do this with Reflection. But is there any better approach ?
I was able to read the JSOn file and pass the strings in the automapper config like this.
var MapperConfig = new MapperConfiguration(c =>
c.CreateMap<InputModel, OutputModel>()
.ForMember("StudentIDColumn", opt => opt.MapFrom("studentid"))
.ForMember("StudentNameColumn", opt => opt.MapFrom("studentname"))
.ForMember("StudentCityColumn", opt => opt.MapFrom("studentcity"))
);

Automapper don't map null sources

I'm using an Automapper and I need to map a List of objects into a nested object. I have these objects:
public abstract class FooSrcBase
{
}
public class FooSrc : FooSrcBase
{
public bool Prop { get; set; }
}
public class FooDest
{
public bool Prop { get; set; }
}
public class FooDestGroup
{
public FooDest FooDest { get; set; }
}
public class Dest
{
public FooDestGroup FooDestGroup { get; set; }
}
I have IEnumerable<FooSrc> which contains FooSrc objects (there are many implementations and only one object per each type may exist in the source) and I need to map it into Dest object. I need this because of mapping into the view models for front end.
When I register mapping like this:
CreateMap<IEnumerable<FooSrc>, Dest>().ForPath(dest => dest.FooDestGroup.FooDest, opt => opt.MapFrom(src => src.FirstOrDefault(x => x.GetType() == typeof(FooSrc))));
CreateMap<FooSrc, FooDest>();
When I map empty list, a problem occurs in Dest object - FooDestGroup is an instance of object, which has a property FooDest with null value.
How it would be possible to make Dest property FooDestGroup map to null, if I provide empty list as a source?
Firstly, your abstract FooSrc class will need a different name (conflicts with your concrete class name FooSrc)
How about modifying the constructor on Dest to avoid the nesting issue?
Automapper is capable of mapping to the constructor parameter automatically, but if you need more advanced behaviour you can refer to https://docs.automapper.org/en/stable/Construction.html
Something like this should work:
public class Dest
{
public FooGroup FooGroup { get; set; }
public Dest(FooDest fooDest)
{
FooGroup = new FooGroup { FooDest = fooDest };
}
}
[..]
Mapper.CreateMap<FooSrc, Dest>();
Mapper.Map<List<Dest>>(listOfFooSrc);
This fixed my problem:
CreateMap<IEnumerable<FooSrcBase>, Dest>()
.ForMember(dest => dest.FooDestGroup, opt => opt.MapFrom(src => src.FirstOrDefault(x => x.GetType() == typeof(FooSrc))));
CreateMap<FooSrc, FooDestGroup>()
.ForMember(dest => dest.FooDest, opt => opt.MapFrom(src => src));
CreateMap<FooSrc, FooDest>();

How to provide additional parameter to Mapper's Map method?

I'm trying to map one list to another, but not everything I want is in the source object. Let me explain:
These are my two objects:
public class SourceItem
{
public int Id { get; set;}
public string Name { get; set;}
}
public class TargetItem
{
public int TargetId { get; set;}
public string TargetName { get; set;}
public int Foo { get; set; }
}
This is how I configure my Mapper:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<SourceItem, TargetItem>()
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.Name));
}
}
When I do this...
var targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems);
...the targets list's objects will have Foo set to 0. Obviously. What I'm trying to do is to also initialize Foo (with the parameter). Like this:
fooParameter = 321;
List<TargetItem> targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems);
foreach(var target in targets)
{
target.Foo = fooParameter;
}
Is it possible, to do it using Mapper?
Thanks in advance!
You can't do exactly what you want, but you can get pretty close by specifying mapping options when you call Map.
fooParameter = 321;
var targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems, opt =>
opt.AfterMap(
(src, dest) => {
foreach (var d in dest)
{
d.Foo = fooParameter;
}
})
);

How to resolve source member for multi-property mapping in AutoMapper?

I have DO and DTO classes:
public class DO
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
public class DTO
{
public int MappedProperty1 { get; set; }
public int MappedProperty2 { get; set; }
}
The mapping registration for the above classes are like below.
// From DO to DTO:
configuration.CreateMap<DO, DTO>()
.ForMember(d => d.MappedProperty1, o => o.MapFrom(s => s.Property1))
.ForMember(d => d.MappedProperty2, o => o.MapFrom(s => s.Property1 + s.Property2));
// From DTO to DO:
configuration.CreateMap<DTO, DO>()
.ForMember(d => d.Property1, o => o.MapFrom(s => s.MappedProperty1))
.ForMember(d => d.Property2, o => o.MapFrom(s => s.MappedProperty1 - s.MappedProperty2));
Elsewhere in the code I need to get the destination property names in the mapping. I'm currently doing this by:
var map= mapper.ConfigurationProvider.FindTypeMapFor<DTO, DO>();
var property = map.GetPropertyMaps().First(f => f.SourceMember == typeof(DTO).GetProperty("MappedProperty1"));
var name = property.DestinationProperty.Name;
This works fine with MappedProperty1. However, it does not work with MappedProperty2 as there are two properties participating in the mapping. Instead, I get the error message:
"Sequence contains no matching element".
I also tried with the following instead of First filter and in debug I can see that the count in the list is zero.
var map= mapper.ConfigurationProvider.FindTypeMapFor<DTO, DO>();
var property = map.GetPropertyMaps().Where(w => w.SourceMember == typeof(DTO).GetProperty("MappedProperty2")).ToList();
How can I achieve this source name resolution for multi-property mapping?

Automapper projection and union

I have a problem with union and automapper projections.
I have two entities:
public class Entity
{
public DateTime ActionDate { get; set; }
public int SomeProp { get; set; }
}
public class ExtendedEntity
{
public DateTime ActionDate { get; set; }
public int SomeProp { get; set; }
public int SomeOtherProp { get; set; }
}
and projection:
public class EntityProjection
{
public DateTime ActionDate { get; set; }
public int SomeProp { get; set; }
public int SomeOtherProp { get; set; }
public string Source { get; set; }
}
i map entities to one projection, Entity does not have SomeOtherProp so i set 0 to it:
public class EntityProfile : Profile
{
protected override void Configure()
{
CreateMap<ExtendedEntity, EntityProjection>()
.ForMember(dest => dest.Source, opt => opt.MapFrom(src => "ext entity"));
CreateMap<Entity, EntityProjection>()
.ForMember(dest => dest.SomeOtherProp, opt => opt.MapFrom(src => 0))
.ForMember(dest => dest.Source, opt => opt.MapFrom(src => "entity"));
}
}
when i try to use next code i get error:
var entities = context.Set<Entity>()
.Project().To<EntityProjection>();
var extEntities = context.Set<ExtendedEntity>()
.Project().To<EntityProjection>();
var result = entities.Union(extEntities).OrderBy(p => p.ActionDate).ToList();
Error text: The type 'UserQuery+EntityProjection' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be...
That means that properties in projection must be initialized in same order, how i can set projection properties initialization order by automapper?
Very late answer, and the short version seems to be "You can't".
I had exactly the same question (Can I force Automapper to initialise properties in a certain order?) and ended up mapping everything within a LINQ select statement.
For ease, I made it a static method within my DTO (cut-down code):
public static IQueryable<MyDto> QueryableFromTaskType1(
IQueryable<TaskType1> query)
{
return query.Select(src => new MyDto()
{
TaskId = src.Id,
AssetTypeName = src.Asset.AssetType.Name,
AssetId = src.Asset.Id,
AssetCode = src.Asset.Code,
AssetName = src.Asset.Name,
});
}
public static IQueryable<MyDto> QueryableFromTaskType2(
IQueryable<TaskType2> query)
{
return query.Select(src => new MyDto()
{
TaskId = src.Id,
AssetTypeName = src.AssetTypeName,
AssetId = src.AssetId,
AssetCode = src.AssetCode,
AssetName = src.AssetName,
});
}
then you can get your objects, as an IQueryable, simply pass them through the appropriate static method (which appends a select into the DTO - or projects as it's otherwise known) and then Union or Concat the resulting IQueryables.
The only downside is that Automapper will normally deal with recursive automapping, although I'm pretty certain that wouldn't map to SQL well anyway, so you probably don't lose much.

Categories

Resources