Automapper - Mapping from source child object to destination is including parent values - c#

I'm currently experiencing an issue when trying to map the entire destination object from a child property on the source object. Something similar as described here: Automapper - How to map from source child object to destination
I've made use of the .ConstructUsing method as described in the link above however I'm seeing some weird behaviour where the outputted, mapped object is getting values from the parent instead of the child.
I made a demo of the problem here: https://dotnetfiddle.net/OdaGUr
Is this a problem with my code, should I be using a different method to achieve what I'm trying to do or is this a fault with AutoMapper?
EDIT:
public static void Main()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Child1, Child2>();
cfg.CreateMap<Parent, Child2>().ConstructUsing((src, ctx) => ctx.Mapper.Map<Child2>(src.Child1));
});
var mapper = config.CreateMapper();
var parent = new Parent{
Id = 1,
Child1 = new Child1 {
Id = 2
}
};
var child2 = mapper.Map<Parent, Child2>(parent);
Console.WriteLine(child2.Id); // Returns 1. Expect this to be 2 from Parent.Child1
}
public class Parent
{
public int Id {get;set;}
public Child1 Child1 {get;set;}
}
public class Child1
{
public int Id {get;set;}
}
public class Child2
{
public int Id {get;set;}
}

ConstructUsing() is used to create the destination object, where the value should be stored in. In your case you are returning a Child2 object with the Id value set to 2 (as returned by the ctx.Mapper.Map<Child1, Child2>(src.Child1) line).
However, after the object has been created, the default mapping will still be applied. This means that the Parent.Id value will be saved in the Child2.Id property, because the names of the property match ("Id"). So, the initial value of 2 will be replaced with the value 1 from the Parent object.
Depending on what you want to do, you might want to use ForMember() to configure special handling on how the property values should be mapped. An example would be:
.ForMember(dest => dest.Id, src => src.MapFrom(it => it.Child1.Id))

Related

Deep cloning via Automapper ignoring specific property from the hierarchy

I have fairly simple question regarding Automapper mapping definition. My intent is to deep clone an object via Automapper while ignoring 'Id' property, this is why i have chosen it to customize the mapping.
public interface IEntity<T>
{
T Id { get; }
}
public abstract class Entity : IEntity<Guid>
{
public Guid Id { get; set; }
}
All my entities are deriving from Entity class and i simply wants to ignore all Id property in the nested hierarchy of my object without being so explicit about the mapping definition.
So far i have come up with the following piece of code to do the cloning but how to ignore Id property mapping for the nested properties and not just for the root.
public static T AutomapperClone<T>(this T source)
where T : IEntity<Guid>
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<T, T>()
.ForMember(d => d.Id, o => o.Ignore());
});
// checking configuration validity
config.AssertConfigurationIsValid();
// creating mapper
var mapper = config.CreateMapper();
var copy = mapper.Map<T, T>(source);
return copy;
}
The idea is that all entities get their new Id instead of using the same mapped ones. Is it accomplishable via Automapper?
Appreciate your feedback.
I wouldn't use Automapper for this person, try AnyClone to do this. It does deep cloning and can ignore by property name which seems to be what you are looking for.

C# Automapper Nested Object

Ive searched for this for few days now and cant seem to get anything to work, I am using c# MVC Entity Framework with Automapper and im trying to achieve the below ViewModels (mainly LostDocumentVM) to be mapped from my database, all other properties will be set in controllers.
Here is my ViewModels...
DocumentVM
{
Public Enum.HistoricType HistoricType {get;set;}
Public DocumentChildVM Document { get; set;}
}
DocumentChildVM
{
Public bool ShowHistoricLink {get;set;}
Public IEnumerable<ListDocumentVM> DocumentsToReview {get;set;}
}
ListDocumentVM
{
Public int Id {get;set;}
Public string Name {get; set;}
Public DateTime? ReviewDate {get;set;}
}
I initialise the DocumentVM like this...
DocumentVM documentVM = DataContext.SystemUser.Where(x=>x.SustemUserID==LoggedOnUserID).Project().To<DocumentVM>().SingleOrDefault();
And my mapping is like this...
Mapper.CreateMap<SystemUser,DocumentVM>()
.ForMember(dest=>dest.Document.DocumentsToReview, opt=>opt.MapFrom(src=>src.Documents.Where(x=>x.DocumentType == Enum.DocumentType.Assessment));
Im new to AutoMapper and struggling to get more advanced mappings to work.
Yes, your ForMember member must refer to a member on the destination type, and yours is referring to a member on the child type. Instead, you'll need to create an AfterMap function that fills in this information on that child entity.
It's not difficult, but you have a bit of a strange set up where a child object Document has a property DocumentsToReview from another property on the parent DocumentVM:
documentVM.Document.DocumentsToReview =
src.Documents.Where(doc => doc.DocumentType == Enum.DocumentType.Assessment);
When you have to shuffle data between sibling/nephew members, it gets a little more challenging.
To do this with AfterMap:
Mapper.CreateMap<SystemUser, DocumentVM>()
.AfterMap((src, dest) => dest.Document.DocumentsToReview =
src.Documents.Where(doc => doc.DocumentType == Enum.DocumentType.Assessment));

AutoMapper include all properties of a custom List<T>

class SomeObject
{
public string name {get;set;}
}
class CustomCollection : List<SomeObject>
{
public int x {get;set;}
public string z {get;set;}
}
class A
{
public CustomCollection collection { get ; set; }
}
class B
{
public CustomCollection collection { get ; set; }
}
// Creating mapping
Mapper.CreateMap<A, B>();
When I Map A to B, all properties get mapped correctly except X and Z in CustomCollection.
CustomCollection correctly gets the List of SomeObject initialized and SomeObject.Name is also mapped correctly.
Only the custom properties X, Z that I've declared in the collection do not get mapped.
What am I doing wrong?
Only way I've found is to do an after mapping like below, but then it kinda defeats the purpose of using automapper and it breaks everytime I add a new property to CustomCollection.
Mapper.CreateMap<A, B>().AfterMap((source, destination) => {
source.x = destination.x;
source.z = destination.z ;
});
Your current mapping configuration does create a new CustomCollection but the SomeObject items inside are references to the objects in the source collection. If that's not an issue you can use the following mapping configuration:
CreateMap<CustomCollection, CustomCollection>()
.AfterMap((source, dest) => dest.AddRange(source));
CreateMap<A, B>();
If your are also fine with b.collection referencing to a.collection you could use the following mapping configuration:
CreateMap<CustomCollection, CustomCollection>()
.ConstructUsing(col => col);
CreateMap<A, B>();
AutoMapper is not designed for cloning so if you need that you must write your own logic for that.

Convert Entity Framework Object to JSON Object

I'm trying to make a generic handler post a JSONJ object based on my entity type SYSTEM_AUDIT_SHEET:
SYSTEM_AUDIT_SHEET sheet = ctx.SYSTEM_AUDIT_SHEET
.Where(s => s.SYSTEM_KEY == system_key_dec)
.Select(s => s)
.OrderByDescending(s => s.AUDIT_SHEET_VERSION)
.First();
HttpContext.Current.Response.Write(serializer.Serialize(sheet));
But I get the following error:
A circular reference was detected while serializing an object of type
'System.Data.Entity.DynamicProxies.SYSTEM_AUDIT_SHEET_521A7B786A51FC0F83641182DD72C8DFE6C082418D30BBB977B403409A74CE17'.
Why can't I convert the entity to JSON?
You cannot convert objects to json that reference themselves as this would create an infinitely long json string.
For example, the following pseudo-code wouldn't work because it sets up a circular reference (Dog >> Bone >> Dog...):
class Dog {
private Bone myBone;
public Dog() {
myBone = new Bone(this);
}
}
class Bone {
private Dog buriedBy;
public Bone(Dog d) {
buriedBy = d;
}
}
There seem to be some good solutions by googling 'json circular reference'. See the top two stack overflow links.
The problem is probably that your SYSTEM_AUDIT_SHEET either contains a property that references instances of type SYSTEM_AUDIT_SHEET, or it contains a property that points to objects that have pointers to SYSTEM_AUDIT_SHEET instances. Serializing such a circle of pointers would result in a serialization process that would never end.
You will need to transform your SYSTEM_AUDIT_SHEET to a type that does not (directly or indirectly) reference itself before doing the serialization. You can create a brand new type and write code to instantiate such a type from your SYSTEM_AUDIT_SHEET (AutoMapper might come in handy here). However, I tend to find that in most cases it is easier to just use an anonymous type:
SYSTEM_AUDIT_SHEET sheet = /*some sheet*/
var json = new {
sheet.Id,
sheet.RevisionNumber,
sheet.Title
};
return serializer.Serialize(json);
EDIT
If you want to use AutoMapper and assuming that your sheet looks something like
class SYSTEM_AUDIT_SHEET
{
public int Id { get; set; }
public SYSTEM_AUDIT_SHEET SomeOtherAuditSheet { get;set;}
public string Title { get;set;}
}
you could create a type like
class JSON_SYSTEM_AUDIT_SHEET
{
public int Id { get; set; }
public int SomeOtherAuditSheetsId { get;set;}
public string Title { get;set;}
}
When your application starts (say, in Application_Start) you configure AutoMapper:
AutoMapper.Mapper.CreateMap<SYSTEM_AUDIT_SHEET, JSON_SYSTEM_AUDIT_SHEET>()
.ForMember(dest => dest.SomeOtherAuditSheetsId, opt => opt.MapFrom(src => src.SomeOtherAuditSheet.Id));
The Id and Title properties will be mapped directly across from SYSTEM_AUDIT_SHEET to JSON_SYSTEM_AUDIT_SHEET because they have the same names in both types. The property SomeOtherAuditSheetsId needs special configuration, because there is no property with that exact name on the source type.
When you want to convert SYSTEM_AUDIT_SHEET to JSON_SYSTEM_AUDIT_SHEET you do:
return AutoMapper.Mapper.Map<SYSTEM_AUDIT_SHEET , JSON_SYSTEM_AUDIT_SHEET >(sheet);
You may want to have a look at AutoMapper's flattening features.
Hope this helps.

Polymorphism with AutoMapper

I have these business classes:
class BaseNode
{
public string name;
}
class CompositeNode : BaseNode
{
public List<BaseNode> childs = new List<BaseNode>();
}
And this flat dto:
class NodeDto
{
public string name;
public List<NodeDto> childs;
}
(note how all derived types are represented by one dto class)
I use auto mapper to do a conversion:
Mapper.CreateMap<BaseNode, NodeDto>()
.Include<CompositeNode, NodeDto>()
.ForMember(s => s.childs, prop => prop.Ignore());
Mapper.CreateMap<CompositeNode, NodeDto>();
Mapper.AssertConfigurationIsValid();
var root = new CompositeNode() { name = "root" };
var child = new CompositeNode {name = "child"};
var child2 = new CompositeNode { name = "child2" };
root.childs.Add(child);
child.childs.Add(child2);
var rootDto = Mapper.Map<CompositeNode, NodeDto>(root);
However the below is always null instead of having the child list:
rootDto.childs[0].childs
(i.e. only first level child is mapped correctly)
If I remove the prop.Ignore part I get an assert error that the childs property is not mapped.
What am I doing wrong?
This is old, but came across it looking for something else... You're telling it to ignore the childs field. AutoMapper is doing what it was told to do.
.ForMember(s => s.childs, prop => prop.Ignore());
You don't have properties in your classes public string Name {get;set;}, you have public Fields, I think that's the problem
also in order to map this classes you only need to create 2 simple maps
Mapper.CreateMap<CompositeNode, NodeDto>();
Mapper.CreateMap<BaseNode, NodeDto>()
.ForMember(s => s.childs, prop => prop.Ignore());;

Categories