AutoMapper - Nested mapping whilst preserving selected child properties - c#

So i have this;
public class Parent
{
public string SomeProperty { get; set; }
public Child ChildProperty { get; set; }
}
public class Child
{
public string ChildProperty { get; set; }
public string OtherChildProperty { get; set; }
}
public class Flat
{
public string SomeProperty { get; set; }
public string ChildProperty { get; set; }
}
And then I do this;
Flat source = new Flat { SomeProperty = "test", ChildProperty = "test" };
Parent dest = GetParentFromDataContext();
Mapper.Map<Flat,Parent>(source,dest);
Then my expectation is that dest.ChildProperty.OtherChildProperty is still set to whatever it was when it was pulled from the datacontext. However I'm struggling to do this.
If I CreateMap as so, then I get a "must resolve to top-level member" exception;
Mapper.CreateMap<Flat,Parent>()
.ForMember(dest => dest.Parent.ChildProperty.ChildProperty,
options => options.MapFrom(source => source.ChildProperty))
.ForMember(dest => dest.Parent.ChildProperty.OtherChildProperty,
options => options.Ignore());
However if I do the following, then the new Child {} replaces the Child pulled from the datacontext essentially clearing OtherChildProperty;
Mapper.CreateMap<Flat,Parent>()
.ForMember(dest => dest.Child
options => options.MapFrom(source => new Child { ChildProperty = source.ChildProperty } ));
How can i map this and preserve the child properties I wish to ignore?

You are trying to reverse flattening process with automapper and that is just wrong tool for the job. See this SO question.

Inelegant, but this works;
Mapper.CreateMap<Flat,Parent>()
.ForMember(dest => dest.ChildProperty, options => options.Ignore());
Mapper.CreateMap<Flat,Child>()
.ForMember(dest => dest.OtherChildProperty, options => options.Ignore());
Mapper.Map<Flat,Parent>(source,dest);
Mapper.Map<Flat,Child>(source,dest.Child);

Related

AutoMapper - Apply ForAllMembers instead of multiple ForMembers

I'm kind of new in the world of AutoMapper, just so you know =)
I have 2 classes:
Class LibraryParameters
public class LibraryParameters
{
public int library_id { get; set; }
public string document_name { get; set; } = string.Empty;
public string template_name { get; set; } = string.Empty;
}
Class LibraryDocument
public class LibraryDocument
{
public int libraryId { get; set; }
public string documentName { get; set; } = string.Empty;
public string templateName { get; set; } = string.Empty;
}
So as you can see the variable names are different. So I use AutoMapper for this problem. I configured AutoMapper and made use of .ForMember as you can see below:
CreateMap<LibraryParameters, LibraryDocument>()
.ForMember(dest => dest.libraryId,
opt => opt.MapFrom(src => src.library_id))
.ForMember(dest => dest.templateName,
opt => opt.MapFrom(src => src.template_name))
.ForMember(dest => dest.documentName,
opt => opt.MapFrom(src => src.document_name));
But is it not possible to avoid these different ForMember methods and use ForAllMembers for example? I can't find any information about this anywhere so you guys are my source of help :)
Instead of using .ForAllMembers(), you need to specify the naming convention for the source as below:
For MapperConfiguration
MapperConfiguration _config = new MapperConfiguration(cfg => {
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
});
For Mapping Profile
public class YourProfile : Profile
{
public YourProfile()
{
SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
}
}
Demo # .NET Fiddle
References
Naming Conventions | AutoMapper

Can AutoMapper mappings be composed?

The models I'm working with include an entry object which I'd like to map as if its child object were the entire object.
Here is a simplified version of the problem. I'd like an instance of OurWrappedSource to map directly to OurTarget.
class OurTarget
{
public Guid Id { get; set; }
public string Notes { get; set; }
public int Quantity { get; set; }
}
class OurSource
{
public Guid Id { get; set; }
public string Notes { get; set; }
public int Quantity { get; set; }
}
class OurWrappedSource
{
public OurSource Source { get; set; }
}
private static void TestUnwrapUsingConfig(MapperConfiguration config)
{
config.AssertConfigurationIsValid();
IMapper mapper = new Mapper(config);
var wrappedSource = new OurWrappedSource
{
Source = new OurSource
{
Id = new Guid("123e4567-e89b-12d3-a456-426655440000"),
Notes = "Why?",
Quantity = 27
}
};
var target = mapper.Map<OurTarget>(wrappedSource);
Assert.Equal(wrappedSource.Source.Id, target.Id);
Assert.Equal(wrappedSource.Source.Notes, target.Notes);
Assert.Equal(wrappedSource.Source.Quantity, target.Quantity);
}
The following configuration works, but is unwieldy for more than a couple of members:
// Works, but isn't *auto* enough
TestUnwrapUsingConfig(new MapperConfiguration(cfg =>
{
cfg.CreateMap<OurWrappedSource, OurTarget>()
.ForMember(src => src.Id, opts => opts.MapFrom(wrappedSource => wrappedSource.Source.Id))
.ForMember(src => src.Notes, opts => opts.MapFrom(wrappedSource => wrappedSource.Source.Notes))
.ForMember(src => src.Quantity, opts => opts.MapFrom(wrappedSource => wrappedSource.Source.Quantity));
}));
What I'd like to be able to do is define two intermediate mappings an then compose them:
Map OurWrappedSource directly to OurSource
Map OurSource directly to OurTarget
Map OurWrappedSource to OurTarget by composing mapping 1 with mapping 2
After some hammering, I have this configuration:
// Works, but #3 probably isn't ProjectTo-friendly
TestUnwrapUsingConfig(new MapperConfiguration(cfg =>
{
// 1
cfg.CreateMap<OurWrappedSource, OurSource>()
.ConvertUsing(wrappedSource => wrappedSource.Source);
// 2
cfg.CreateMap<OurSource, OurTarget>();
// 3
cfg.CreateMap<OurWrappedSource, OurTarget>()
.ConstructUsing((wrappedSource, ctx) =>
ctx.Mapper.Map<OurTarget>(ctx.Mapper.Map<OurSource>(wrappedSource))
)
.ForAllOtherMembers(opts => opts.Ignore());
}));
This works exactly as specified, but mapping 3 seems perhaps a little more explicit and/or kludgey than it should. It involves code in a Func (rather than an expression), which makes me think it probably won't optimize well when used with ProjectTo(). Is there a way to rewrite mapping 3 to address these issues?

Automapper flattened DTO to Parent with Child Collection

I have a flattened DTO which I need to map to a Parent with Children relationship. I'd like to do this via AutoMapper as I'm using it in other places and it works great. I've seen examples of mapping a Parent and Child but not when the Child is a collection and the source is a flattened DTO. I've created some classes that I can use for getting the configuration correct. Below are my sample classes:
public class Parent
{
public int ParentId { get; set; }
public string ParentName { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
public int ChildId { get; set; }
public string ChildName { get; set; }
}
public class ParentChildDTO
{
public int ParentId { get; set; }
public string ParentName { get; set; }
public int ChildId { get; set; }
public string ChildName { get; set; }
}
I'm performing the mapper initialization on startup. I'm not getting any errors until I try to perform the mapping. Below is my mapper initialization code. I've kept in the commented out line to show the other way that I've tried to accomplish this:
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<ParentChildDTO, Child>();
cfg.CreateMap<ParentChildDTO, Parent>()
.ForMember(dest => dest.Children, opt => opt.MapFrom(src => src));
//.ForMember(dest => dest.Children, opt => opt.MapFrom(src => new Child { ChildId = src.ChildId, ChildName = src.ChildName }));
});
Below is my code that I'm using for trying to perform the mapping configuration:
ParentChildDTO parentChildDTO = new ParentChildDTO { ParentId = 1, ParentName = "Parent Name", ChildId = 2, ChildName = "Child Name" };
Parent parent = AutoMapper.Mapper.Map<ParentChildDTO, Parent>(parentChildDTO);
List<LienActivity> mapTest = AutoMapper.Mapper.Map<List<BaseActivityUploadDTO>, List<LienActivity>>(request.Activities);
I've considered using a Custom Value Resolver, but was hoping to avoid the complexity and extra code if what I'm doing is possible with the correct configuration.
Here's the error that I get with the above code:
Error mapping types.
Mapping types: ParentChildDTO -> Parent
Type Map configuration: ParentChildDTO -> Parent
Property: Children
Here is another option where you define custom mapping just for the Children property.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ParentChildDTO, Parent>()
.ForMember(d => d.Children,
opt => opt.MapFrom(src => new List<Child>() { new Child() { ChildId = src.ChildId, ChildName = src.ChildName } }));
});
Given what you are going to use this for based on your comments - the below configuration should do the job (parent properties are resolved by default AutoMapper conventions so no need to explicitly map):
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<ParentChildDTO, Parent>()
.ConstructUsing(item => new Parent
{
Children = new List<Child>
{
new Child
{
ChildId = item.ChildId,
ChildName = item.ChildName
}
}
});
});

Map List of Customers with automapper

Using Automapper I am trying to map one object to another. One property is a class called Task containing a list of customers. The other class is called Result and contains a count of customers as well as another list of customers.
This is my current approach which fills information into the order properties correctly, but fails in result, which is still null. How can I get the List into result? How do i need to change the maps and do i need to create a map in both directions, or this this not necessary?
Mapper.Initialize(cfg =>
{
cfg.CreateMap<CustomerPost.RootObject, Customers.RootObject>();
cfg.CreateMap<CustomerPost.Order, Customers.Order>();
cfg.CreateMap<Customers.Result, CustomerPost.Task>();
cfg.CreateMap<CustomerPost.Task, Customers.Result>()
.ForMember(x => x.customerscount, opt => opt.Ignore())
.ForMember(x => x.customerstotalcount, opt => opt.Ignore());
});
try
{
Mapper.AssertConfigurationIsValid();
}
catch (AutoMapperConfigurationException ex)
{
//TODO: Handle this
throw ex;
}
var customer = Mapper.Map<CustomerPost.RootObject, Customers.RootObject>(input);
here are my current classes (Customer):
public class Result
{
public int customerstotalcount { get; set; }
public int customerscount { get; set; }
public List<Customer> customers { get; set; }
}
public class RootObject
{
public Status status { get; set; }
public Order order { get; set; }
public Result result { get; set; }
}
CustomerPost:
public class Task
{
public List<Customer> customers { get; set; }
}
public class RootObject
{
public Order order { get; set; }
public List<Task> tasks { get; set; }
}
Okay so the solution to my problem was that my mapping didn't find "result" so i've just mapped my RootObject like this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<CustomerPost.RootObject, Customers.RootObject>()
.ForMember(x => x.status, opt => opt.Ignore())
.ForMember(x => x.order, opt => opt.Ignore())
.ForMember(dest => dest.result, src => src.MapFrom(opt => opt.tasks.FirstOrDefault()));
then i went ahead and just mapped the result like this:
var result = Mapper.Map<CustomerPost.Task, Customers.Result>(input.tasks.FirstOrDefault());
var customer = new Customers.Customer();
customer = result.customers.FirstOrDefault();
and just bound it to a new Customerobject. Then all my information got transmitted as expected

resolving complex entity model to flat model view using AutoMapper

I want to create a map for a somewhat complex entity model to a flattened view Model
My entity model is like so
cbItems
has many cbItemsContent
has many cbRegulators
so my viewmodels are like so
for cbItems:
public class ItemViewModel
{
public ItemViewModel()
{
this.CbItemsContents = new HashSet<ItemContentViewModel>();
}
public int ItemID { get; set; }
......
public virtual ICollection<ItemContentViewModel> CbItemsContents { get; set; }
}
}
for cbItemsContent:
public class ItemContentViewModel
{
public int ItemContentID { get; set; }
public int ItemID { get; set; }
....
public ItemContentRegulatorsViewModel RegulatedBy { get; set; }
}
}
for cbRegulators:
public class ItemContentRegulatorsViewModel
{
public int ItemContentId { get; set; }
public IEnumerable<int> RegulatorIds { get; set; }
}
}
I had hoped it would be as easy as this:
config.CreateMap<CbItem, ItemViewModel>();
config.CreateMap<CbItemsContent, ItemContentViewModel>()
.ForMember(dest => dest.RegulatedBy.ItemContentId,
m => m.MapFrom(src => src.GenericID))
.ForMember(dest => dest.RegulatedBy.RegulatorIds,
n => n.MapFrom(src => src.cbItemsContentRegulators.Select(q => q.cbRegulator.RegulatorId)));
from teh following query:
ItemViewModel item =
_context.cbItems.Where(u => u.ItemId = id)
.ProjectTo<ItemViewModel>()
.first();
But this results in an error:
Expression 'dest => dest.RegulatedBy.ItemContentId' 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
HOw can I achieve my desired model layout?
You have to map ItemContentRegulatorsViewModel, then you don't need to set it from the ViewModel above.
#Rabban probably means something like this:
config.CreateMap<CbItemsContent, ItemContentViewModel>()
.ForMember(dest => dest.RegulatedBy, o => o.MapFrom(src => src));
config.CreateMap<CbItemsContent, ItemContentRegulatorsViewModel>()
.ForMember(dest => dest.ItemContentId, o => o.MapFrom(src => src.GenericID))
.ForMember(dest => dest.RegulatorIds, o => o.MapFrom(src => src.cbItemsContentRegulators.Select(q => q.cbRegulator.RegulatorId)));

Categories