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());;
Related
I am using automapper 9.0.0.
My situation is as follows. I have a list of items which are all instances of an abstract base class, let's call it BaseClass. There are 2 classes that inherit that class, let's call those Bar1Class and Bar2Class.
I want to map a list of BaseClass to an object that contains 2 lists. One list is a DTO for the Bar1Class objects from the list, and the 2nd one is for the Bar2Class objects from the list:
List<BaseClass> items = GetItems();
var dto = Mapper.Map<FooResponseModel>(items);
The hierarchy is as follows:
// Response models
public class FooResponseModel
{
public IEnumerable<Bar1Model> Bar1Models {get;set;}
public IEnumerable<Bar2Model> Bar2Models {get;set;}
}
public class Bar1Model
{
public string MyString {get;set;}
public int MyInt {get;set;}
}
public class Bar2Model
{
public string MyString {get;set;}
public bool MyBool {get;set;}
}
public abstract class BaseClass
{
public string MyString {get;set;}
}
public class Bar1Class : BaseClass
{
public int MyInt {get;set;}
}
public class Bar2Class : BaseClass
{
public bool MyBool {get;set;}
}
How would I set this up?
Using the CreateMap<BaseClass, FooResponseModel>() doesn't really work because I can't divide the collections. Doing something like CreateMap<Bar1Class, Bar1Model>() would allow me to map the classes itself, but not allow me to set up the lists.
Thanks!
Edit:
I would map it by hand like this now, because i dont know how to map the upper object correctly. I would of course add CreateMap<Bar1Class, Bar1Model>() and such beforehand.
var dto = new FooResponseModel
{
Bar1Models = items
.Where(x => x is Bar1Class)
.Cast<Bar1Class>()
.Select(x => Mapper.Map<Bar1Model>()),
Bar2Models = items.
.Where(x => x is Bar2Class)
.Cast<Bar2Class>()
.Select(x => Mapper.Map<Bar2Model>())
}
Create two maps between source classes and destination classes. Then, add mapping from a sequence of BaseClass to FooResponseModel and point AutoMapper how to populate Bar1Models and Bar2Models properties.
CreateMap<Bar1Class, Bar1Model>();
CreateMap<Bar2Class, Bar2Model>();
CreateMap<IEnumerable<BaseClass>, FooResponseModel>()
.ForMember(d => d.Bar1Models, o => o.MapFrom(s => s.Where(b => b is Bar1Class)))
.ForMember(d => d.Bar2Models, o => o.MapFrom(s => s.Where(b => b is Bar2Class)));
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))
If I have an collection of a given entity, I'm able to obtain the properties for entity like so:
var myCollection = new List<Foo>();
entities.GetType().GetGenericArguments()[0].GetProperties().Dump();
However, I'm having some difficulties listing out the properties if my collection is an IEnumerable of a base class and populated with derived classes.
public class Foo
{
public string One {get;set;}
}
public class Bar : Foo
{
public string Hello {get;set;}
public string World {get;set;}
}
// "Hello", "World", and "One" contained in the PropertyInfo[] collection
var barCollection = new List<Bar>() { new Bar() };
barCollection.GetType().GetGenericArguments()[0].GetProperties().Dump();
// Only "One" exists in the PropertyInfo[] collection
var fooCollection = new List<Foo>() { new Bar() };
fooCollection.GetType().GetGenericArguments()[0].GetProperties().Dump();
Is there anyway to get the types of the items in the collection even though the collection is declared using the base class?
That is because you are obtaining the properties from the type represented by the type parameter T which is Foo, and Foo only has the One property.
To get all possible properties, you need to go through the types of all the objects in the list like this:
var allProperties = fooCollection
.Select(x => x.GetType())
.Distinct()
.SelectMany(t => t.GetProperties())
.ToList();
By default automapper creates a new object based on the destination's type:
public void Doit( Person personMissingStuff )
{
PersonTemplate template = _personDao.GetPersonTemplate(1);
Mapper.CreateMap<PersonTemplate, Person>();
Person basePerson = Mapper.Map<Person>( template );
Mapper.CreateMap<Person, Person>();
Person completePerson =
Mapper.Map<Person, Person>( basePerson, personMissingStuff );
...
}
Instead of getting a completePerson I just get a basePerson again. How do I tell AutoMapper to run the mappings by reference instead of by value?
Mapper.Map(source, dest) actually returns the destination object, in your case it'll be personMissingStuff.
With that said, assuming that you want to fill in only the null properties in the destination, you need to configure the mapping properly, and not map when the destination property has value.
The following sample does exactly this for class properties. For value properties, probably you need to do additional configuration. The example uses NUnit and SharpTestsEx:
[TestFixture]
public class LoadIntoInstance
{
public class Template
{
public string Name { get; set; }
}
public class Person
{
public string Name { get; set; }
public string OtherData { get; set; }
}
[Test]
public void Should_load_into_instance()
{
Mapper.CreateMap<Template, Person>()
.ForMember(d=>d.OtherData, opt=>opt.Ignore());
Mapper.CreateMap<Person, Person>()
.ForAllMembers(opt=>opt.Condition(ctx=>ctx.DestinationValue==null));
Mapper.AssertConfigurationIsValid();
var template = new Template {Name = "template"};
var basePerson = Mapper.Map<Person>(template);
var noNamePerson = new Person {OtherData = "other"};
var result = Mapper.Map(basePerson, noNamePerson);
result.Should().Be.SameInstanceAs(noNamePerson);
result.Satisfy(r =>
r.Name == "template" &&
r.OtherData == "other");
}
}
Just use traditional shallow cloning...
Person completePerson = basePerson.MemberwiseClone();
This should keep the reference types and clone the value types.
MSDN Link
Let us say we have a simple business object:
class SimpleBO
{
public string Field1{get;set;}
public string Field2{get;set;}
}
Also we have a complex Aggregate like that:
class ComplexBO
{
public SimpleBO SimpleBOField {get;set}
public List<SomeClass> ObjectList {get;set;}
public SomeClass Object {get;set;}
}
SomeClass itself has a reference of SimpleBO:
class SomeClass
{
public SimpleBO SimpleBOField {get;set}
}
Now in some part of my program I want to get a list of all distinct simple objects met inside a certain aggreggate. We are using automapper heavily but I did not manage to map it so far. May be a LINQ query is a better option? How would you solve this?
Assuming what you have is:
ComplexBO aggregate = ...
then you should just need:
var objects = aggregate.ObjectList.Select(x => x.SimpleBOField).Concat(
new[] {aggregate.SimpleBOField, aggregate.Object.SimpleBOField }
).Distinct().ToList();
This will give you the distinct object references; if you need distinct value pairs, then either override Equals()/GetHashCode(), or cheat:
var objects = aggregate.ObjectList.Select(x => x.SimpleBOField).Concat(
new[] {aggregate.SimpleBOField, aggregate.Object.SimpleBOField }
).Select(
x => new {x.SimpleBOField.Field1, x.SimpleBOField.Field2}
).Distinct().Select(
x => new SimpleBO {Field1 = x.Field1, Field2 = x.Field2}
).ToList();